Add SETTINGS_NO_RFC7540_PRIORITIES

Add SETTINGS_NO_RFC7540_PRIORITIES to disable RFC7540 priorities.  If
disabled, streams are served in FIFO.
This commit is contained in:
Tatsuhiro Tsujikawa 2022-06-07 18:06:30 +09:00
parent 8d48686cec
commit 9812a0bc81
16 changed files with 548 additions and 40 deletions

View File

@ -703,7 +703,12 @@ typedef enum {
* SETTINGS_ENABLE_CONNECT_PROTOCOL
* (`RFC 8441 <https://tools.ietf.org/html/rfc8441>`_)
*/
NGHTTP2_SETTINGS_ENABLE_CONNECT_PROTOCOL = 0x08
NGHTTP2_SETTINGS_ENABLE_CONNECT_PROTOCOL = 0x08,
/**
* SETTINGS_NO_RFC7540_PRIORITIES (`RFC 9218
* <https://datatracker.ietf.org/doc/html/rfc9218>`_)
*/
NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES = 0x09
} nghttp2_settings_id;
/* Note: If we add SETTINGS, update the capacity of
NGHTTP2_INBOUND_NUM_IV as well */
@ -2693,6 +2698,11 @@ nghttp2_option_set_max_deflate_dynamic_table_size(nghttp2_option *option,
* This option prevents the library from retaining closed streams to
* maintain the priority tree. If this option is set to nonzero,
* applications can discard closed stream completely to save memory.
*
* If
* :enum:`nghttp2_settings_id.NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES`
* of value of 1 is submitted via `nghttp2_submit_settings()`, any
* closed streams are not retained regardless of this option.
*/
NGHTTP2_EXTERN void nghttp2_option_set_no_closed_streams(nghttp2_option *option,
int val);
@ -3589,6 +3599,11 @@ NGHTTP2_EXTERN int nghttp2_session_consume_stream(nghttp2_session *session,
* found, we use default priority instead of given |pri_spec|. That
* is make stream depend on root stream with weight 16.
*
* If
* :enum:`nghttp2_settings_id.NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES`
* of value of 1 is submitted via `nghttp2_submit_settings()`, this
* function does nothing and returns 0.
*
* This function returns 0 if it succeeds, or one of the following
* negative error codes:
*
@ -3632,6 +3647,11 @@ nghttp2_session_change_stream_priority(nghttp2_session *session,
* found, we use default priority instead of given |pri_spec|. That
* is make stream depend on root stream with weight 16.
*
* If
* :enum:`nghttp2_settings_id.NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES`
* of value of 1 is submitted via `nghttp2_submit_settings()`, this
* function does nothing and returns 0.
*
* This function returns 0 if it succeeds, or one of the following
* negative error codes:
*
@ -3837,6 +3857,11 @@ nghttp2_priority_spec_check_default(const nghttp2_priority_spec *pri_spec);
* :macro:`NGHTTP2_MAX_WEIGHT`, it becomes
* :macro:`NGHTTP2_MAX_WEIGHT`.
*
* If
* :enum:`nghttp2_settings_id.NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES`
* of value of 1 is received by a remote endpoint, |pri_spec| is
* ignored, and treated as if ``NULL`` is specified.
*
* The |nva| is an array of name/value pair :type:`nghttp2_nv` with
* |nvlen| elements. The application is responsible to include
* required pseudo-header fields (header field whose name starts with
@ -4057,6 +4082,11 @@ NGHTTP2_EXTERN int nghttp2_submit_trailer(nghttp2_session *session,
* :macro:`NGHTTP2_MIN_WEIGHT`. If it is strictly greater than
* :macro:`NGHTTP2_MAX_WEIGHT`, it becomes :macro:`NGHTTP2_MAX_WEIGHT`.
*
* If
* :enum:`nghttp2_settings_id.NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES`
* of value of 1 is received by a remote endpoint, |pri_spec| is
* ignored, and treated as if ``NULL`` is specified.
*
* The |nva| is an array of name/value pair :type:`nghttp2_nv` with
* |nvlen| elements. The application is responsible to include
* required pseudo-header fields (header field whose name starts with
@ -4184,6 +4214,11 @@ NGHTTP2_EXTERN int nghttp2_submit_data(nghttp2_session *session, uint8_t flags,
* :macro:`NGHTTP2_MAX_WEIGHT`, it becomes
* :macro:`NGHTTP2_MAX_WEIGHT`.
*
* If
* :enum:`nghttp2_settings_id.NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES`
* of value of 1 is received by a remote endpoint, this function does
* nothing and returns 0.
*
* This function returns 0 if it succeeds, or one of the following
* negative error codes:
*

View File

@ -1071,6 +1071,11 @@ int nghttp2_iv_check(const nghttp2_settings_entry *iv, size_t niv) {
return 0;
}
break;
case NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES:
if (iv[i].value != 0 && iv[i].value != 1) {
return 0;
}
break;
}
}
return 1;

View File

@ -385,6 +385,7 @@ static void init_settings(nghttp2_settings_storage *settings) {
settings->initial_window_size = NGHTTP2_INITIAL_WINDOW_SIZE;
settings->max_frame_size = NGHTTP2_MAX_FRAME_SIZE_MIN;
settings->max_header_list_size = UINT32_MAX;
settings->no_rfc7540_priorities = UINT32_MAX;
}
static void active_outbound_item_reset(nghttp2_active_outbound_item *aob,
@ -398,6 +399,15 @@ static void active_outbound_item_reset(nghttp2_active_outbound_item *aob,
aob->state = NGHTTP2_OB_POP_ITEM;
}
static int stream_less(const void *lhsx, const void *rhsx) {
const nghttp2_stream *lhs, *rhs;
lhs = nghttp2_struct_of(lhsx, nghttp2_stream, pq_entry);
rhs = nghttp2_struct_of(rhsx, nghttp2_stream, pq_entry);
return lhs->seq < rhs->seq;
}
int nghttp2_enable_strict_preface = 1;
static int session_new(nghttp2_session **session_ptr,
@ -442,6 +452,7 @@ static int session_new(nghttp2_session **session_ptr,
(*session_ptr)->pending_local_max_concurrent_stream =
NGHTTP2_DEFAULT_MAX_CONCURRENT_STREAMS;
(*session_ptr)->pending_enable_push = 1;
(*session_ptr)->pending_no_rfc7540_priorities = UINT8_MAX;
if (server) {
(*session_ptr)->server = 1;
@ -584,6 +595,12 @@ static int session_new(nghttp2_session **session_ptr,
}
}
rv = nghttp2_pq_init(&(*session_ptr)->ob_data, stream_less, mem);
if (rv != 0) {
assert(0);
abort();
}
return 0;
fail_aob_framebuf:
@ -748,6 +765,7 @@ void nghttp2_session_del(nghttp2_session *session) {
settings = next;
}
nghttp2_pq_free(&session->ob_data);
nghttp2_stream_free(&session->root);
/* Have to free streams first, so that we can check
@ -775,6 +793,7 @@ int nghttp2_session_reprioritize_stream(
nghttp2_priority_spec pri_spec_default;
const nghttp2_priority_spec *pri_spec = pri_spec_in;
assert(session->pending_no_rfc7540_priorities != 1);
assert(pri_spec->stream_id != stream->stream_id);
if (!nghttp2_stream_in_dep_tree(stream)) {
@ -842,6 +861,104 @@ int nghttp2_session_reprioritize_stream(
return 0;
}
static int session_ob_data_push(nghttp2_session *session,
nghttp2_stream *stream) {
int rv;
assert(stream->flags & NGHTTP2_STREAM_FLAG_NO_RFC7540_PRIORITIES);
assert(stream->queued == 0);
rv = nghttp2_pq_push(&session->ob_data, &stream->pq_entry);
if (rv != 0) {
return rv;
}
stream->queued = 1;
return 0;
}
static int session_ob_data_remove(nghttp2_session *session,
nghttp2_stream *stream) {
assert(stream->flags & NGHTTP2_STREAM_FLAG_NO_RFC7540_PRIORITIES);
assert(stream->queued == 1);
nghttp2_pq_remove(&session->ob_data, &stream->pq_entry);
stream->queued = 0;
return 0;
}
static int session_attach_stream_item(nghttp2_session *session,
nghttp2_stream *stream,
nghttp2_outbound_item *item) {
int rv;
rv = nghttp2_stream_attach_item(stream, item);
if (rv != 0) {
return rv;
}
if (!(stream->flags & NGHTTP2_STREAM_FLAG_NO_RFC7540_PRIORITIES)) {
return 0;
}
return session_ob_data_push(session, stream);
}
static int session_detach_stream_item(nghttp2_session *session,
nghttp2_stream *stream) {
int rv;
rv = nghttp2_stream_detach_item(stream);
if (rv != 0) {
return rv;
}
if (!(stream->flags & NGHTTP2_STREAM_FLAG_NO_RFC7540_PRIORITIES) ||
!stream->queued) {
return 0;
}
return session_ob_data_remove(session, stream);
}
static int session_defer_stream_item(nghttp2_session *session,
nghttp2_stream *stream, uint8_t flags) {
int rv;
rv = nghttp2_stream_defer_item(stream, flags);
if (rv != 0) {
return rv;
}
if (!(stream->flags & NGHTTP2_STREAM_FLAG_NO_RFC7540_PRIORITIES) ||
!stream->queued) {
return 0;
}
return session_ob_data_remove(session, stream);
}
static int session_resume_deferred_stream_item(nghttp2_session *session,
nghttp2_stream *stream,
uint8_t flags) {
int rv;
rv = nghttp2_stream_resume_deferred_item(stream, flags);
if (rv != 0) {
return rv;
}
if (!(stream->flags & NGHTTP2_STREAM_FLAG_NO_RFC7540_PRIORITIES) ||
(stream->flags & NGHTTP2_STREAM_FLAG_DEFERRED_ALL)) {
return 0;
}
return session_ob_data_push(session, stream);
}
int nghttp2_session_add_item(nghttp2_session *session,
nghttp2_outbound_item *item) {
/* TODO Return error if stream is not found for the frame requiring
@ -863,7 +980,7 @@ int nghttp2_session_add_item(nghttp2_session *session,
return NGHTTP2_ERR_DATA_EXIST;
}
rv = nghttp2_stream_attach_item(stream, item);
rv = session_attach_stream_item(session, stream, item);
if (rv != 0) {
return rv;
@ -1041,11 +1158,20 @@ nghttp2_stream *nghttp2_session_open_stream(nghttp2_session *session,
if (stream) {
assert(stream->state == NGHTTP2_STREAM_IDLE);
assert(nghttp2_stream_in_dep_tree(stream));
nghttp2_session_detach_idle_stream(session, stream);
rv = nghttp2_stream_dep_remove(stream);
if (rv != 0) {
return NULL;
assert((stream->flags & NGHTTP2_STREAM_FLAG_NO_RFC7540_PRIORITIES) ||
nghttp2_stream_in_dep_tree(stream));
if (nghttp2_stream_in_dep_tree(stream)) {
assert(!(stream->flags & NGHTTP2_STREAM_FLAG_NO_RFC7540_PRIORITIES));
nghttp2_session_detach_idle_stream(session, stream);
rv = nghttp2_stream_dep_remove(stream);
if (rv != 0) {
return NULL;
}
if (session->pending_no_rfc7540_priorities == 1) {
stream->flags |= NGHTTP2_STREAM_FLAG_NO_RFC7540_PRIORITIES;
}
}
} else {
stream = nghttp2_mem_malloc(mem, sizeof(nghttp2_stream));
@ -1056,7 +1182,21 @@ nghttp2_stream *nghttp2_session_open_stream(nghttp2_session *session,
stream_alloc = 1;
}
if (pri_spec->stream_id != 0) {
if (session->pending_no_rfc7540_priorities == 1 ||
session->remote_settings.no_rfc7540_priorities == 1) {
/* For client which has not received server
SETTINGS_NO_RFC7540_PRIORITIES = 1, send a priority signal
opportunistically. */
if (session->server ||
session->remote_settings.no_rfc7540_priorities == 1) {
nghttp2_priority_spec_default_init(&pri_spec_default);
pri_spec = &pri_spec_default;
}
if (session->pending_no_rfc7540_priorities == 1) {
flags |= NGHTTP2_STREAM_FLAG_NO_RFC7540_PRIORITIES;
}
} else if (pri_spec->stream_id != 0) {
dep_stream = nghttp2_session_get_stream_raw(session, pri_spec->stream_id);
if (!dep_stream &&
@ -1102,6 +1242,10 @@ nghttp2_stream *nghttp2_session_open_stream(nghttp2_session *session,
(int32_t)session->local_settings.initial_window_size,
stream_user_data, mem);
if (session->pending_no_rfc7540_priorities == 1) {
stream->seq = session->stream_seq++;
}
rv = nghttp2_map_insert(&session->streams, stream_id, stream);
if (rv != 0) {
nghttp2_stream_free(stream);
@ -1141,6 +1285,10 @@ nghttp2_stream *nghttp2_session_open_stream(nghttp2_session *session,
}
}
if (stream->flags & NGHTTP2_STREAM_FLAG_NO_RFC7540_PRIORITIES) {
return stream;
}
if (pri_spec->stream_id == 0) {
dep_stream = &session->root;
}
@ -1180,7 +1328,7 @@ int nghttp2_session_close_stream(nghttp2_session *session, int32_t stream_id,
item = stream->item;
rv = nghttp2_stream_detach_item(stream);
rv = session_detach_stream_item(session, stream);
if (rv != 0) {
return rv;
@ -1230,6 +1378,10 @@ int nghttp2_session_close_stream(nghttp2_session *session, int32_t stream_id,
/* Closes both directions just in case they are not closed yet */
stream->flags |= NGHTTP2_STREAM_FLAG_CLOSED;
if (session->pending_no_rfc7540_priorities == 1) {
return nghttp2_session_destroy_stream(session, stream);
}
if ((session->opt_flags & NGHTTP2_OPTMASK_NO_CLOSED_STREAMS) == 0 &&
session->server && !is_my_stream_id &&
nghttp2_stream_in_dep_tree(stream)) {
@ -2013,7 +2165,7 @@ static int session_prep_frame(nghttp2_session *session,
if (stream) {
int rv2;
rv2 = nghttp2_stream_detach_item(stream);
rv2 = session_detach_stream_item(session, stream);
if (nghttp2_is_fatal(rv2)) {
return rv2;
@ -2032,7 +2184,7 @@ static int session_prep_frame(nghttp2_session *session,
queue when session->remote_window_size > 0 */
assert(session->remote_window_size > 0);
rv = nghttp2_stream_defer_item(stream,
rv = session_defer_stream_item(session, stream,
NGHTTP2_STREAM_FLAG_DEFERRED_FLOW_CONTROL);
if (nghttp2_is_fatal(rv)) {
@ -2051,7 +2203,8 @@ static int session_prep_frame(nghttp2_session *session,
return rv;
}
if (rv == NGHTTP2_ERR_DEFERRED) {
rv = nghttp2_stream_defer_item(stream, NGHTTP2_STREAM_FLAG_DEFERRED_USER);
rv = session_defer_stream_item(session, stream,
NGHTTP2_STREAM_FLAG_DEFERRED_USER);
if (nghttp2_is_fatal(rv)) {
return rv;
@ -2062,7 +2215,7 @@ static int session_prep_frame(nghttp2_session *session,
return NGHTTP2_ERR_DEFERRED;
}
if (rv == NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE) {
rv = nghttp2_stream_detach_item(stream);
rv = session_detach_stream_item(session, stream);
if (nghttp2_is_fatal(rv)) {
return rv;
@ -2078,7 +2231,7 @@ static int session_prep_frame(nghttp2_session *session,
if (rv != 0) {
int rv2;
rv2 = nghttp2_stream_detach_item(stream);
rv2 = session_detach_stream_item(session, stream);
if (nghttp2_is_fatal(rv2)) {
return rv2;
@ -2339,6 +2492,10 @@ static int session_prep_frame(nghttp2_session *session,
nghttp2_outbound_item *
nghttp2_session_get_next_ob_item(nghttp2_session *session) {
nghttp2_outbound_item *item;
nghttp2_pq_entry *ent;
nghttp2_stream *stream;
if (nghttp2_outbound_queue_top(&session->ob_urgent)) {
return nghttp2_outbound_queue_top(&session->ob_urgent);
}
@ -2354,7 +2511,18 @@ nghttp2_session_get_next_ob_item(nghttp2_session *session) {
}
if (session->remote_window_size > 0) {
return nghttp2_stream_next_outbound_item(&session->root);
item = nghttp2_stream_next_outbound_item(&session->root);
if (item) {
return item;
}
ent = nghttp2_pq_top(&session->ob_data);
if (!ent) {
return NULL;
}
stream = nghttp2_struct_of(ent, nghttp2_stream, pq_entry);
return stream->item;
}
return NULL;
@ -2363,6 +2531,8 @@ nghttp2_session_get_next_ob_item(nghttp2_session *session) {
nghttp2_outbound_item *
nghttp2_session_pop_next_ob_item(nghttp2_session *session) {
nghttp2_outbound_item *item;
nghttp2_pq_entry *ent;
nghttp2_stream *stream;
item = nghttp2_outbound_queue_top(&session->ob_urgent);
if (item) {
@ -2388,7 +2558,18 @@ nghttp2_session_pop_next_ob_item(nghttp2_session *session) {
}
if (session->remote_window_size > 0) {
return nghttp2_stream_next_outbound_item(&session->root);
item = nghttp2_stream_next_outbound_item(&session->root);
if (item) {
return item;
}
ent = nghttp2_pq_top(&session->ob_data);
if (!ent) {
return NULL;
}
stream = nghttp2_struct_of(ent, nghttp2_stream, pq_entry);
return stream->item;
}
return NULL;
@ -2550,7 +2731,7 @@ static int session_after_frame_sent1(nghttp2_session *session) {
}
if (stream && aux_data->eof) {
rv = nghttp2_stream_detach_item(stream);
rv = session_detach_stream_item(session, stream);
if (nghttp2_is_fatal(rv)) {
return rv;
}
@ -2675,9 +2856,8 @@ static int session_after_frame_sent1(nghttp2_session *session) {
}
}
case NGHTTP2_PRIORITY:
if (session->server) {
if (session->server || session->pending_no_rfc7540_priorities == 1) {
return 0;
;
}
stream = nghttp2_session_get_stream_raw(session, frame->hd.stream_id);
@ -2852,7 +3032,7 @@ static int session_after_frame_sent2(nghttp2_session *session) {
further data. */
if (nghttp2_session_predicate_data_send(session, stream) != 0) {
if (stream) {
rv = nghttp2_stream_detach_item(stream);
rv = session_detach_stream_item(session, stream);
if (nghttp2_is_fatal(rv)) {
return rv;
@ -3150,7 +3330,7 @@ static ssize_t nghttp2_session_mem_send_internal(nghttp2_session *session,
}
if (rv == NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE) {
rv = nghttp2_stream_detach_item(stream);
rv = session_detach_stream_item(session, stream);
if (nghttp2_is_fatal(rv)) {
return rv;
@ -4091,6 +4271,8 @@ int nghttp2_session_on_priority_received(nghttp2_session *session,
int rv;
nghttp2_stream *stream;
assert(session->pending_no_rfc7540_priorities != 1);
if (frame->hd.stream_id == 0) {
return session_handle_invalid_connection(session, frame, NGHTTP2_ERR_PROTO,
"PRIORITY: stream_id == 0");
@ -4148,6 +4330,8 @@ static int session_process_priority_frame(nghttp2_session *session) {
nghttp2_inbound_frame *iframe = &session->iframe;
nghttp2_frame *frame = &iframe->frame;
assert(session->pending_no_rfc7540_priorities != 1);
nghttp2_frame_unpack_priority_payload(&frame->priority, iframe->sbuf.pos);
return nghttp2_session_on_priority_received(session, frame);
@ -4214,8 +4398,8 @@ static int update_remote_initial_window_size_func(void *entry, void *ptr) {
if (stream->remote_window_size > 0 &&
nghttp2_stream_check_deferred_by_flow_control(stream)) {
rv = nghttp2_stream_resume_deferred_item(
stream, NGHTTP2_STREAM_FLAG_DEFERRED_FLOW_CONTROL);
rv = session_resume_deferred_stream_item(
arg->session, stream, NGHTTP2_STREAM_FLAG_DEFERRED_FLOW_CONTROL);
if (nghttp2_is_fatal(rv)) {
return rv;
@ -4389,6 +4573,9 @@ int nghttp2_session_update_local_settings(nghttp2_session *session,
case NGHTTP2_SETTINGS_ENABLE_CONNECT_PROTOCOL:
session->local_settings.enable_connect_protocol = iv[i].value;
break;
case NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES:
session->local_settings.no_rfc7540_priorities = iv[i].value;
break;
}
}
@ -4547,10 +4734,32 @@ int nghttp2_session_on_settings_received(nghttp2_session *session,
session->remote_settings.enable_connect_protocol = entry->value;
break;
case NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES:
if (entry->value != 0 && entry->value != 1) {
return session_handle_invalid_connection(
session, frame, NGHTTP2_ERR_PROTO,
"SETTINGS: invalid SETTINGS_NO_RFC7540_PRIORITIES");
}
if (session->remote_settings.no_rfc7540_priorities != UINT32_MAX &&
session->remote_settings.no_rfc7540_priorities != entry->value) {
return session_handle_invalid_connection(
session, frame, NGHTTP2_ERR_PROTO,
"SETTINGS: SETTINGS_NO_RFC7540_PRIORITIES cannot be changed");
}
session->remote_settings.no_rfc7540_priorities = entry->value;
break;
}
}
if (session->remote_settings.no_rfc7540_priorities == UINT32_MAX) {
session->remote_settings.no_rfc7540_priorities = 0;
}
if (!noack && !session_is_closing(session)) {
rv = nghttp2_session_add_settings(session, NGHTTP2_FLAG_ACK, NULL, 0);
@ -4833,8 +5042,8 @@ static int session_on_stream_window_update_received(nghttp2_session *session,
if (stream->remote_window_size > 0 &&
nghttp2_stream_check_deferred_by_flow_control(stream)) {
rv = nghttp2_stream_resume_deferred_item(
stream, NGHTTP2_STREAM_FLAG_DEFERRED_FLOW_CONTROL);
rv = session_resume_deferred_stream_item(
session, stream, NGHTTP2_STREAM_FLAG_DEFERRED_FLOW_CONTROL);
if (nghttp2_is_fatal(rv)) {
return rv;
@ -5276,6 +5485,7 @@ static void inbound_frame_set_settings_entry(nghttp2_inbound_frame *iframe) {
case NGHTTP2_SETTINGS_MAX_FRAME_SIZE:
case NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE:
case NGHTTP2_SETTINGS_ENABLE_CONNECT_PROTOCOL:
case NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES:
break;
default:
DEBUGF("recv: unknown settings id=0x%02x\n", iv.settings_id);
@ -5995,13 +6205,16 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session, const uint8_t *in,
break;
case NGHTTP2_PRIORITY:
rv = session_process_priority_frame(session);
if (nghttp2_is_fatal(rv)) {
return rv;
}
if (session->pending_no_rfc7540_priorities != 1 &&
session->remote_settings.no_rfc7540_priorities != 1) {
rv = session_process_priority_frame(session);
if (nghttp2_is_fatal(rv)) {
return rv;
}
if (iframe->state == NGHTTP2_IB_IGN_ALL) {
return (ssize_t)inlen;
if (iframe->state == NGHTTP2_IB_IGN_ALL) {
return (ssize_t)inlen;
}
}
session_inbound_frame_reset(session);
@ -6870,7 +7083,8 @@ int nghttp2_session_want_write(nghttp2_session *session) {
*/
return session->aob.item || nghttp2_outbound_queue_top(&session->ob_urgent) ||
nghttp2_outbound_queue_top(&session->ob_reg) ||
(!nghttp2_pq_empty(&session->root.obq) &&
((!nghttp2_pq_empty(&session->root.obq) ||
!nghttp2_pq_empty(&session->ob_data)) &&
session->remote_window_size > 0) ||
(nghttp2_outbound_queue_top(&session->ob_syn) &&
!session_is_outgoing_concurrent_streams_max(session));
@ -7023,6 +7237,7 @@ int nghttp2_session_add_settings(nghttp2_session *session, uint8_t flags,
int rv;
nghttp2_mem *mem;
nghttp2_inflight_settings *inflight_settings = NULL;
uint8_t no_rfc7540_pri = session->pending_no_rfc7540_priorities;
mem = &session->mem;
@ -7040,6 +7255,21 @@ int nghttp2_session_add_settings(nghttp2_session *session, uint8_t flags,
return NGHTTP2_ERR_INVALID_ARGUMENT;
}
for (i = 0; i < niv; ++i) {
if (iv[i].settings_id != NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES) {
continue;
}
if (no_rfc7540_pri == UINT8_MAX) {
no_rfc7540_pri = (uint8_t)iv[i].value;
continue;
}
if (iv[i].value != (uint32_t)no_rfc7540_pri) {
return NGHTTP2_ERR_INVALID_ARGUMENT;
}
}
item = nghttp2_mem_malloc(mem, sizeof(nghttp2_outbound_item));
if (item == NULL) {
return NGHTTP2_ERR_NOMEM;
@ -7114,6 +7344,12 @@ int nghttp2_session_add_settings(nghttp2_session *session, uint8_t flags,
}
}
if (no_rfc7540_pri == UINT8_MAX) {
session->pending_no_rfc7540_priorities = 0;
} else {
session->pending_no_rfc7540_priorities = no_rfc7540_pri;
}
return 0;
}
@ -7242,7 +7478,9 @@ int nghttp2_session_pack_data(nghttp2_session *session, nghttp2_bufs *bufs,
return rv;
}
reschedule_stream(stream);
if (!(stream->flags & NGHTTP2_STREAM_FLAG_NO_RFC7540_PRIORITIES)) {
reschedule_stream(stream);
}
if (frame->hd.length == 0 && (data_flags & NGHTTP2_DATA_FLAG_EOF) &&
(data_flags & NGHTTP2_DATA_FLAG_NO_END_STREAM)) {
@ -7316,7 +7554,7 @@ int nghttp2_session_resume_data(nghttp2_session *session, int32_t stream_id) {
return NGHTTP2_ERR_INVALID_ARGUMENT;
}
rv = nghttp2_stream_resume_deferred_item(stream,
rv = session_resume_deferred_stream_item(session, stream,
NGHTTP2_STREAM_FLAG_DEFERRED_USER);
if (nghttp2_is_fatal(rv)) {
@ -7424,6 +7662,8 @@ uint32_t nghttp2_session_get_remote_settings(nghttp2_session *session,
return session->remote_settings.max_header_list_size;
case NGHTTP2_SETTINGS_ENABLE_CONNECT_PROTOCOL:
return session->remote_settings.enable_connect_protocol;
case NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES:
return session->remote_settings.no_rfc7540_priorities;
}
assert(0);
@ -7447,6 +7687,8 @@ uint32_t nghttp2_session_get_local_settings(nghttp2_session *session,
return session->local_settings.max_header_list_size;
case NGHTTP2_SETTINGS_ENABLE_CONNECT_PROTOCOL:
return session->local_settings.enable_connect_protocol;
case NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES:
return session->local_settings.no_rfc7540_priorities;
}
assert(0);
@ -7730,6 +7972,10 @@ int nghttp2_session_change_stream_priority(
nghttp2_stream *stream;
nghttp2_priority_spec pri_spec_copy;
if (session->pending_no_rfc7540_priorities == 1) {
return 0;
}
if (stream_id == 0 || stream_id == pri_spec->stream_id) {
return NGHTTP2_ERR_INVALID_ARGUMENT;
}
@ -7762,6 +8008,10 @@ int nghttp2_session_create_idle_stream(nghttp2_session *session,
nghttp2_stream *stream;
nghttp2_priority_spec pri_spec_copy;
if (session->pending_no_rfc7540_priorities == 1) {
return 0;
}
if (stream_id == 0 || stream_id == pri_spec->stream_id ||
!session_detect_idle_stream(session, stream_id)) {
return NGHTTP2_ERR_INVALID_ARGUMENT;

View File

@ -165,6 +165,7 @@ typedef struct {
uint32_t max_frame_size;
uint32_t max_header_list_size;
uint32_t enable_connect_protocol;
uint32_t no_rfc7540_priorities;
} nghttp2_settings_storage;
typedef enum {
@ -202,6 +203,9 @@ struct nghttp2_session {
response) frame, which are subject to
SETTINGS_MAX_CONCURRENT_STREAMS limit. */
nghttp2_outbound_queue ob_syn;
/* Queue for DATA frames which is used when
SETTINGS_NO_RFC7540_PRIORITIES is enabled. */
nghttp2_pq ob_data;
nghttp2_active_outbound_item aob;
nghttp2_inbound_frame iframe;
nghttp2_hd_deflater hd_deflater;
@ -227,6 +231,9 @@ struct nghttp2_session {
/* Queue of In-flight SETTINGS values. SETTINGS bearing ACK is not
considered as in-flight. */
nghttp2_inflight_settings *inflight_settings_head;
/* Sequential number across all streams to process streams in
FIFO. */
uint64_t stream_seq;
/* The number of outgoing streams. This will be capped by
remote_settings.max_concurrent_streams. */
size_t num_outgoing_streams;
@ -328,6 +335,9 @@ struct nghttp2_session {
/* Unacked local ENABLE_CONNECT_PROTOCOL value. We use this to
accept :protocol header field before SETTINGS_ACK is received. */
uint8_t pending_enable_connect_protocol;
/* Unacked local SETTINGS_NO_RFC7540_PRIORITIES value, which is
effective before it is acknowledged. */
uint8_t pending_no_rfc7540_priorities;
/* Nonzero if the session is server side. */
uint8_t server;
/* Flags indicating GOAWAY is sent and/or received. The flags are

View File

@ -484,6 +484,10 @@ int nghttp2_stream_attach_item(nghttp2_stream *stream,
stream->item = item;
if (stream->flags & NGHTTP2_STREAM_FLAG_NO_RFC7540_PRIORITIES) {
return 0;
}
rv = stream_update_dep_on_attach_item(stream);
if (rv != 0) {
/* This may relave stream->queued == 1, but stream->item == NULL.
@ -503,6 +507,10 @@ int nghttp2_stream_detach_item(nghttp2_stream *stream) {
stream->item = NULL;
stream->flags = (uint8_t)(stream->flags & ~NGHTTP2_STREAM_FLAG_DEFERRED_ALL);
if (stream->flags & NGHTTP2_STREAM_FLAG_NO_RFC7540_PRIORITIES) {
return 0;
}
return stream_update_dep_on_detach_item(stream);
}
@ -514,6 +522,10 @@ int nghttp2_stream_defer_item(nghttp2_stream *stream, uint8_t flags) {
stream->flags |= flags;
if (stream->flags & NGHTTP2_STREAM_FLAG_NO_RFC7540_PRIORITIES) {
return 0;
}
return stream_update_dep_on_detach_item(stream);
}
@ -529,6 +541,10 @@ int nghttp2_stream_resume_deferred_item(nghttp2_stream *stream, uint8_t flags) {
return 0;
}
if (stream->flags & NGHTTP2_STREAM_FLAG_NO_RFC7540_PRIORITIES) {
return 0;
}
return stream_update_dep_on_attach_item(stream);
}

View File

@ -90,8 +90,10 @@ typedef enum {
NGHTTP2_STREAM_FLAG_DEFERRED_USER = 0x08,
/* bitwise OR of NGHTTP2_STREAM_FLAG_DEFERRED_FLOW_CONTROL and
NGHTTP2_STREAM_FLAG_DEFERRED_USER. */
NGHTTP2_STREAM_FLAG_DEFERRED_ALL = 0x0c
NGHTTP2_STREAM_FLAG_DEFERRED_ALL = 0x0c,
/* Indicates that this stream is not subject to RFC7540
priorities scheme. */
NGHTTP2_STREAM_FLAG_NO_RFC7540_PRIORITIES = 0x10,
} nghttp2_stream_flag;
/* HTTP related flags to enforce HTTP semantics */

View File

@ -196,7 +196,8 @@ int32_t nghttp2_submit_headers(nghttp2_session *session, uint8_t flags,
flags &= NGHTTP2_FLAG_END_STREAM;
if (pri_spec && !nghttp2_priority_spec_check_default(pri_spec)) {
if (pri_spec && !nghttp2_priority_spec_check_default(pri_spec) &&
session->remote_settings.no_rfc7540_priorities != 1) {
rv = detect_self_dependency(session, stream_id, pri_spec);
if (rv != 0) {
return rv;
@ -229,6 +230,10 @@ int nghttp2_submit_priority(nghttp2_session *session, uint8_t flags,
mem = &session->mem;
if (session->remote_settings.no_rfc7540_priorities == 1) {
return 0;
}
if (stream_id == 0 || pri_spec == NULL) {
return NGHTTP2_ERR_INVALID_ARGUMENT;
}
@ -688,7 +693,8 @@ int32_t nghttp2_submit_request(nghttp2_session *session,
return NGHTTP2_ERR_PROTO;
}
if (pri_spec && !nghttp2_priority_spec_check_default(pri_spec)) {
if (pri_spec && !nghttp2_priority_spec_check_default(pri_spec) &&
session->remote_settings.no_rfc7540_priorities != 1) {
rv = detect_self_dependency(session, -1, pri_spec);
if (rv != 0) {
return rv;

View File

@ -114,7 +114,8 @@ Config::Config()
hexdump(false),
echo_upload(false),
no_content_length(false),
ktls(false) {}
ktls(false),
no_rfc7540_pri(false) {}
Config::~Config() {}
@ -864,6 +865,12 @@ int Http2Handler::connection_made() {
++niv;
}
if (config->no_rfc7540_pri) {
entry[niv].settings_id = NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES;
entry[niv].value = 1;
++niv;
}
r = nghttp2_submit_settings(session_, NGHTTP2_FLAG_NONE, entry.data(), niv);
if (r != 0) {
return r;

View File

@ -83,6 +83,7 @@ struct Config {
bool echo_upload;
bool no_content_length;
bool ktls;
bool no_rfc7540_pri;
Config();
~Config();
};

View File

@ -79,6 +79,8 @@ const char *strsettingsid(int32_t id) {
return "SETTINGS_MAX_HEADER_LIST_SIZE";
case NGHTTP2_SETTINGS_ENABLE_CONNECT_PROTOCOL:
return "SETTINGS_ENABLE_CONNECT_PROTOCOL";
case NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES:
return "SETTINGS_NO_RFC7540_PRIORITIES";
default:
return "UNKNOWN";
}

View File

@ -123,7 +123,8 @@ Config::Config()
no_push(false),
expect_continue(false),
verify_peer(true),
ktls(false) {
ktls(false),
no_rfc7540_pri(false) {
nghttp2_option_new(&http2_option);
nghttp2_option_set_peer_max_concurrent_streams(http2_option,
peer_max_concurrent_streams);
@ -935,6 +936,12 @@ size_t populate_settings(nghttp2_settings_entry *iv) {
++niv;
}
if (config.no_rfc7540_pri) {
iv[niv].settings_id = NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES;
iv[niv].value = 1;
++niv;
}
return niv;
}
} // namespace
@ -2757,6 +2764,8 @@ Options:
Suppress warning on server certificate verification
failure.
--ktls Enable ktls.
--no-rfc7540-pri
Disable RFC7540 priorities.
--version Display version information and exit.
-h, --help Display this help and exit.
@ -2813,6 +2822,7 @@ int main(int argc, char **argv) {
{"expect-continue", no_argument, &flag, 13},
{"encoder-header-table-size", required_argument, &flag, 14},
{"ktls", no_argument, &flag, 15},
{"no-rfc7540-pri", no_argument, &flag, 16},
{nullptr, 0, nullptr, 0}};
int option_index = 0;
int c =
@ -3044,6 +3054,10 @@ int main(int argc, char **argv) {
// ktls option
config.ktls = true;
break;
case 16:
// no-rfc7540-pri option
config.no_rfc7540_pri = true;
break;
}
break;
default:

View File

@ -98,6 +98,7 @@ struct Config {
bool expect_continue;
bool verify_peer;
bool ktls;
bool no_rfc7540_pri;
};
enum class RequestState { INITIAL, ON_REQUEST, ON_RESPONSE, ON_COMPLETE };

View File

@ -179,6 +179,8 @@ Options:
--no-content-length
Don't send content-length header field.
--ktls Enable ktls.
--no-rfc7540-pri
Disable RFC7540 priorities.
--version Display version information and exit.
-h, --help Display this help and exit.
@ -230,6 +232,7 @@ int main(int argc, char **argv) {
{"no-content-length", no_argument, &flag, 10},
{"encoder-header-table-size", required_argument, &flag, 11},
{"ktls", no_argument, &flag, 12},
{"no-rfc7540-pri", no_argument, &flag, 13},
{nullptr, 0, nullptr, 0}};
int option_index = 0;
int c = getopt_long(argc, argv, "DVb:c:d:ehm:n:p:va:w:W:", long_options,
@ -413,6 +416,10 @@ int main(int argc, char **argv) {
// tls option
config.ktls = true;
break;
case 13:
// no-rfc7540-pri option
config.no_rfc7540_pri = true;
break;
}
break;
default:

View File

@ -329,6 +329,8 @@ int main(void) {
test_nghttp2_session_no_closed_streams) ||
!CU_add_test(pSuite, "session_set_stream_user_data",
test_nghttp2_session_set_stream_user_data) ||
!CU_add_test(pSuite, "session_no_rfc7540_priorities",
test_nghttp2_session_no_rfc7540_priorities) ||
!CU_add_test(pSuite, "http_mandatory_headers",
test_nghttp2_http_mandatory_headers) ||
!CU_add_test(pSuite, "http_content_length",

View File

@ -3651,6 +3651,29 @@ void test_nghttp2_session_on_settings_received(void) {
nghttp2_session_del(session);
nghttp2_option_del(option);
/* It is invalid to change SETTINGS_NO_RFC7540_PRIORITIES in the
following SETTINGS. */
nghttp2_session_client_new(&session, &callbacks, NULL);
session->remote_settings.no_rfc7540_priorities = 1;
iv[0].settings_id = NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES;
iv[0].value = 0;
nghttp2_frame_settings_init(&frame.settings, NGHTTP2_FLAG_NONE, dup_iv(iv, 1),
1);
CU_ASSERT(0 == nghttp2_session_on_settings_received(session, &frame, 0));
nghttp2_frame_settings_free(&frame.settings, mem);
item = nghttp2_session_get_next_ob_item(session);
CU_ASSERT(NULL != item);
CU_ASSERT(NGHTTP2_GOAWAY == item->frame.hd.type);
nghttp2_session_del(session);
}
void test_nghttp2_session_on_push_promise_received(void) {
@ -5796,6 +5819,37 @@ void test_nghttp2_submit_settings(void) {
CU_ASSERT(50 == session->pending_local_max_concurrent_stream);
nghttp2_session_del(session);
/* Bail out if there are contradicting
SETTINGS_NO_RFC7540_PRIORITIES in one SETTINGS. */
nghttp2_session_server_new(&session, &callbacks, &ud);
iv[0].settings_id = NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES;
iv[0].value = 1;
iv[1].settings_id = NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES;
iv[1].value = 0;
CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT ==
nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, iv, 2));
nghttp2_session_del(session);
/* Attempt to change SETTINGS_NO_RFC7540_PRIORITIES in the 2nd
SETTINGS. */
nghttp2_session_server_new(&session, &callbacks, &ud);
iv[0].settings_id = NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES;
iv[0].value = 1;
CU_ASSERT(0 == nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, iv, 1));
iv[0].settings_id = NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES;
iv[0].value = 0;
CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT ==
nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, iv, 1));
nghttp2_session_del(session);
}
void test_nghttp2_submit_settings_update_local_window_size(void) {
@ -11118,6 +11172,101 @@ void test_nghttp2_session_set_stream_user_data(void) {
nghttp2_session_del(session);
}
void test_nghttp2_session_no_rfc7540_priorities(void) {
nghttp2_session *session;
nghttp2_session_callbacks callbacks;
nghttp2_data_provider data_prd;
my_user_data ud;
nghttp2_outbound_item *item;
nghttp2_mem *mem;
nghttp2_settings_entry iv;
nghttp2_priority_spec pri_spec;
mem = nghttp2_mem_default();
memset(&callbacks, 0, sizeof(nghttp2_session_callbacks));
callbacks.send_callback = null_send_callback;
/* Do not use a dependency tree if SETTINGS_NO_RFC7540_PRIORITIES =
1. */
data_prd.read_callback = fixed_length_data_source_read_callback;
ud.data_source_length = 128 * 1024;
CU_ASSERT(0 == nghttp2_session_server_new(&session, &callbacks, &ud));
iv.settings_id = NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES;
iv.value = 1;
CU_ASSERT(0 == nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, &iv, 1));
CU_ASSERT(0 == nghttp2_session_send(session));
open_recv_stream2(session, 1, NGHTTP2_STREAM_OPENING);
CU_ASSERT(0 == nghttp2_submit_response(session, 1, resnv, ARRLEN(resnv),
&data_prd));
item = nghttp2_session_get_next_ob_item(session);
CU_ASSERT(ARRLEN(resnv) == item->frame.headers.nvlen);
assert_nv_equal(resnv, item->frame.headers.nva, item->frame.headers.nvlen,
mem);
CU_ASSERT(0 == nghttp2_session_send(session));
CU_ASSERT(1 == nghttp2_pq_size(&session->ob_data));
CU_ASSERT(nghttp2_pq_empty(&session->root.obq));
nghttp2_session_del(session);
/* Priorities are sent as is before client receives
SETTINGS_NO_RFC7540_PRIORITIES = 1 from server. */
CU_ASSERT(0 == nghttp2_session_client_new(&session, &callbacks, NULL));
iv.settings_id = NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES;
iv.value = 1;
CU_ASSERT(0 == nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, &iv, 1));
pri_spec.stream_id = 5;
pri_spec.weight = 111;
pri_spec.exclusive = 1;
CU_ASSERT(1 == nghttp2_submit_request(session, &pri_spec, reqnv,
ARRLEN(reqnv), NULL, NULL));
item = nghttp2_outbound_queue_top(&session->ob_syn);
CU_ASSERT(NULL != item);
CU_ASSERT(NGHTTP2_HEADERS == item->frame.hd.type);
CU_ASSERT(pri_spec.stream_id == item->frame.headers.pri_spec.stream_id);
CU_ASSERT(pri_spec.weight == item->frame.headers.pri_spec.weight);
CU_ASSERT(pri_spec.exclusive == item->frame.headers.pri_spec.exclusive);
nghttp2_session_del(session);
/* Priorities are defaulted if client received
SETTINGS_NO_RFC7540_PRIORITIES = 1 from server. */
CU_ASSERT(0 == nghttp2_session_client_new(&session, &callbacks, NULL));
iv.settings_id = NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES;
iv.value = 1;
CU_ASSERT(0 == nghttp2_submit_settings(session, NGHTTP2_FLAG_NONE, &iv, 1));
session->remote_settings.no_rfc7540_priorities = 1;
pri_spec.stream_id = 5;
pri_spec.weight = 111;
pri_spec.exclusive = 1;
CU_ASSERT(1 == nghttp2_submit_request(session, &pri_spec, reqnv,
ARRLEN(reqnv), NULL, NULL));
item = nghttp2_outbound_queue_top(&session->ob_syn);
CU_ASSERT(NULL != item);
CU_ASSERT(NGHTTP2_HEADERS == item->frame.hd.type);
CU_ASSERT(nghttp2_priority_spec_check_default(&item->frame.headers.pri_spec));
nghttp2_session_del(session);
}
static void check_nghttp2_http_recv_headers_fail(
nghttp2_session *session, nghttp2_hd_deflater *deflater, int32_t stream_id,
int stream_state, const nghttp2_nv *nva, size_t nvlen) {

View File

@ -162,6 +162,7 @@ void test_nghttp2_session_removed_closed_stream(void);
void test_nghttp2_session_pause_data(void);
void test_nghttp2_session_no_closed_streams(void);
void test_nghttp2_session_set_stream_user_data(void);
void test_nghttp2_session_no_rfc7540_priorities(void);
void test_nghttp2_http_mandatory_headers(void);
void test_nghttp2_http_content_length(void);
void test_nghttp2_http_content_length_mismatch(void);