diff --git a/lib/includes/nghttp2/nghttp2.h b/lib/includes/nghttp2/nghttp2.h index 04321a65..57c0a945 100644 --- a/lib/includes/nghttp2/nghttp2.h +++ b/lib/includes/nghttp2/nghttp2.h @@ -703,7 +703,12 @@ typedef enum { * SETTINGS_ENABLE_CONNECT_PROTOCOL * (`RFC 8441 `_) */ - NGHTTP2_SETTINGS_ENABLE_CONNECT_PROTOCOL = 0x08 + NGHTTP2_SETTINGS_ENABLE_CONNECT_PROTOCOL = 0x08, + /** + * SETTINGS_NO_RFC7540_PRIORITIES (`RFC 9218 + * `_) + */ + 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: * diff --git a/lib/nghttp2_frame.c b/lib/nghttp2_frame.c index 3648b238..b0637f68 100644 --- a/lib/nghttp2_frame.c +++ b/lib/nghttp2_frame.c @@ -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; diff --git a/lib/nghttp2_session.c b/lib/nghttp2_session.c index a84eb832..c7ddbfa1 100644 --- a/lib/nghttp2_session.c +++ b/lib/nghttp2_session.c @@ -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; diff --git a/lib/nghttp2_session.h b/lib/nghttp2_session.h index 907b1704..d72dac2d 100644 --- a/lib/nghttp2_session.h +++ b/lib/nghttp2_session.h @@ -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 diff --git a/lib/nghttp2_stream.c b/lib/nghttp2_stream.c index f4c80a24..c3b55e1d 100644 --- a/lib/nghttp2_stream.c +++ b/lib/nghttp2_stream.c @@ -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); } diff --git a/lib/nghttp2_stream.h b/lib/nghttp2_stream.h index 2846c6aa..88265708 100644 --- a/lib/nghttp2_stream.h +++ b/lib/nghttp2_stream.h @@ -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 */ diff --git a/lib/nghttp2_submit.c b/lib/nghttp2_submit.c index 92fb03e8..499ae835 100644 --- a/lib/nghttp2_submit.c +++ b/lib/nghttp2_submit.c @@ -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; diff --git a/src/HttpServer.cc b/src/HttpServer.cc index 12149c67..004bbd3a 100644 --- a/src/HttpServer.cc +++ b/src/HttpServer.cc @@ -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; diff --git a/src/HttpServer.h b/src/HttpServer.h index 7776ab6e..cde34798 100644 --- a/src/HttpServer.h +++ b/src/HttpServer.h @@ -83,6 +83,7 @@ struct Config { bool echo_upload; bool no_content_length; bool ktls; + bool no_rfc7540_pri; Config(); ~Config(); }; diff --git a/src/app_helper.cc b/src/app_helper.cc index 107423b0..485197e8 100644 --- a/src/app_helper.cc +++ b/src/app_helper.cc @@ -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"; } diff --git a/src/nghttp.cc b/src/nghttp.cc index e52e6825..06ca7888 100644 --- a/src/nghttp.cc +++ b/src/nghttp.cc @@ -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: diff --git a/src/nghttp.h b/src/nghttp.h index 807cc0ef..a8804142 100644 --- a/src/nghttp.h +++ b/src/nghttp.h @@ -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 }; diff --git a/src/nghttpd.cc b/src/nghttpd.cc index 6de8cfa4..8fd66993 100644 --- a/src/nghttpd.cc +++ b/src/nghttpd.cc @@ -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: diff --git a/tests/main.c b/tests/main.c index dc41c7c7..c015f6ae 100644 --- a/tests/main.c +++ b/tests/main.c @@ -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", diff --git a/tests/nghttp2_session_test.c b/tests/nghttp2_session_test.c index 71f34766..9a95ad62 100644 --- a/tests/nghttp2_session_test.c +++ b/tests/nghttp2_session_test.c @@ -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) { diff --git a/tests/nghttp2_session_test.h b/tests/nghttp2_session_test.h index bdedd849..335a7cfc 100644 --- a/tests/nghttp2_session_test.h +++ b/tests/nghttp2_session_test.h @@ -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);