diff options
Diffstat (limited to 'lib/nghttp2_session.c')
-rw-r--r-- | lib/nghttp2_session.c | 840 |
1 files changed, 744 insertions, 96 deletions
diff --git a/lib/nghttp2_session.c b/lib/nghttp2_session.c index 9df3d6f..93f3f07 100644 --- a/lib/nghttp2_session.c +++ b/lib/nghttp2_session.c @@ -36,6 +36,7 @@ #include "nghttp2_option.h" #include "nghttp2_http.h" #include "nghttp2_pq.h" +#include "nghttp2_extpri.h" #include "nghttp2_debug.h" /* @@ -143,6 +144,11 @@ static int session_detect_idle_stream(nghttp2_session *session, return 0; } +static int session_no_rfc7540_pri_no_fallback(nghttp2_session *session) { + return session->pending_no_rfc7540_priorities == 1 && + !session->fallback_rfc7540_priorities; +} + static int check_ext_type_set(const uint8_t *ext_types, uint8_t type) { return (ext_types[type / 8] & (1 << (type & 0x7))) > 0; } @@ -354,6 +360,14 @@ static void session_inbound_frame_reset(nghttp2_session *session) { } nghttp2_frame_origin_free(&iframe->frame.ext, mem); break; + case NGHTTP2_PRIORITY_UPDATE: + if ((session->builtin_recv_ext_types & + NGHTTP2_TYPEMASK_PRIORITY_UPDATE) == 0) { + break; + } + /* Do not call nghttp2_frame_priority_update_free, because all + fields point to sbuf. */ + break; } } @@ -385,6 +399,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 +413,21 @@ static void active_outbound_item_reset(nghttp2_active_outbound_item *aob, aob->state = NGHTTP2_OB_POP_ITEM; } +#define NGHTTP2_STREAM_MAX_CYCLE_GAP ((uint64_t)NGHTTP2_MAX_FRAME_SIZE_MAX) + +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); + + if (lhs->cycle == rhs->cycle) { + return lhs->seq < rhs->seq; + } + + return rhs->cycle - lhs->cycle <= NGHTTP2_STREAM_MAX_CYCLE_GAP; +} + int nghttp2_enable_strict_preface = 1; static int session_new(nghttp2_session **session_ptr, @@ -408,6 +438,7 @@ static int session_new(nghttp2_session **session_ptr, size_t nbuffer; size_t max_deflate_dynamic_table_size = NGHTTP2_HD_DEFAULT_MAX_DEFLATE_BUFFER_SIZE; + size_t i; if (mem == NULL) { mem = nghttp2_mem_default(); @@ -442,6 +473,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; @@ -458,6 +490,7 @@ static int session_new(nghttp2_session **session_ptr, (*session_ptr)->max_send_header_block_length = NGHTTP2_MAX_HEADERSLEN; (*session_ptr)->max_outbound_ack = NGHTTP2_DEFAULT_MAX_OBQ_FLOOD_ITEM; + (*session_ptr)->max_settings = NGHTTP2_DEFAULT_MAX_SETTINGS; if (option) { if ((option->opt_set_mask & NGHTTP2_OPT_NO_AUTO_WINDOW_UPDATE) && @@ -521,6 +554,25 @@ static int session_new(nghttp2_session **session_ptr, if (option->opt_set_mask & NGHTTP2_OPT_MAX_OUTBOUND_ACK) { (*session_ptr)->max_outbound_ack = option->max_outbound_ack; } + + if ((option->opt_set_mask & NGHTTP2_OPT_MAX_SETTINGS) && + option->max_settings) { + (*session_ptr)->max_settings = option->max_settings; + } + + if ((option->opt_set_mask & + NGHTTP2_OPT_SERVER_FALLBACK_RFC7540_PRIORITIES) && + option->server_fallback_rfc7540_priorities) { + (*session_ptr)->opt_flags |= + NGHTTP2_OPTMASK_SERVER_FALLBACK_RFC7540_PRIORITIES; + } + + if ((option->opt_set_mask & + NGHTTP2_OPT_NO_RFC9113_LEADING_AND_TRAILING_WS_VALIDATION) && + option->no_rfc9113_leading_and_trailing_ws_validation) { + (*session_ptr)->opt_flags |= + NGHTTP2_OPTMASK_NO_RFC9113_LEADING_AND_TRAILING_WS_VALIDATION; + } } rv = nghttp2_hd_deflate_init2(&(*session_ptr)->hd_deflater, @@ -578,6 +630,10 @@ static int session_new(nghttp2_session **session_ptr, } } + for (i = 0; i < NGHTTP2_EXTPRI_URGENCY_LEVELS; ++i) { + nghttp2_pq_init(&(*session_ptr)->sched[i].ob_data, stream_less, mem); + } + return 0; fail_aob_framebuf: @@ -660,7 +716,7 @@ int nghttp2_session_server_new3(nghttp2_session **session_ptr, return 0; } -static int free_streams(nghttp2_map_entry *entry, void *ptr) { +static int free_streams(void *entry, void *ptr) { nghttp2_session *session; nghttp2_stream *stream; nghttp2_outbound_item *item; @@ -729,6 +785,7 @@ static void inflight_settings_del(nghttp2_inflight_settings *settings, void nghttp2_session_del(nghttp2_session *session) { nghttp2_mem *mem; nghttp2_inflight_settings *settings; + size_t i; if (session == NULL) { return; @@ -742,6 +799,9 @@ void nghttp2_session_del(nghttp2_session *session) { settings = next; } + for (i = 0; i < NGHTTP2_EXTPRI_URGENCY_LEVELS; ++i) { + nghttp2_pq_free(&session->sched[i].ob_data); + } nghttp2_stream_free(&session->root); /* Have to free streams first, so that we can check @@ -769,6 +829,8 @@ int nghttp2_session_reprioritize_stream( nghttp2_priority_spec pri_spec_default; const nghttp2_priority_spec *pri_spec = pri_spec_in; + assert((!session->server && session->pending_no_rfc7540_priorities != 1) || + (session->server && !session_no_rfc7540_pri_no_fallback(session))); assert(pri_spec->stream_id != stream->stream_id); if (!nghttp2_stream_in_dep_tree(stream)) { @@ -836,6 +898,214 @@ int nghttp2_session_reprioritize_stream( return 0; } +static uint64_t pq_get_first_cycle(nghttp2_pq *pq) { + nghttp2_stream *stream; + + if (nghttp2_pq_empty(pq)) { + return 0; + } + + stream = nghttp2_struct_of(nghttp2_pq_top(pq), nghttp2_stream, pq_entry); + return stream->cycle; +} + +static int session_ob_data_push(nghttp2_session *session, + nghttp2_stream *stream) { + int rv; + uint32_t urgency; + int inc; + nghttp2_pq *pq; + + assert(stream->flags & NGHTTP2_STREAM_FLAG_NO_RFC7540_PRIORITIES); + assert(stream->queued == 0); + + urgency = nghttp2_extpri_uint8_urgency(stream->extpri); + inc = nghttp2_extpri_uint8_inc(stream->extpri); + + assert(urgency < NGHTTP2_EXTPRI_URGENCY_LEVELS); + + pq = &session->sched[urgency].ob_data; + + stream->cycle = pq_get_first_cycle(pq); + if (inc) { + stream->cycle += stream->last_writelen; + } + + rv = nghttp2_pq_push(pq, &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) { + uint32_t urgency; + + assert(stream->flags & NGHTTP2_STREAM_FLAG_NO_RFC7540_PRIORITIES); + assert(stream->queued == 1); + + urgency = nghttp2_extpri_uint8_urgency(stream->extpri); + + assert(urgency < NGHTTP2_EXTPRI_URGENCY_LEVELS); + + nghttp2_pq_remove(&session->sched[urgency].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); +} + +static nghttp2_outbound_item * +session_sched_get_next_outbound_item(nghttp2_session *session) { + size_t i; + nghttp2_pq_entry *ent; + nghttp2_stream *stream; + + for (i = 0; i < NGHTTP2_EXTPRI_URGENCY_LEVELS; ++i) { + ent = nghttp2_pq_top(&session->sched[i].ob_data); + if (!ent) { + continue; + } + + stream = nghttp2_struct_of(ent, nghttp2_stream, pq_entry); + return stream->item; + } + + return NULL; +} + +static int session_sched_empty(nghttp2_session *session) { + size_t i; + + for (i = 0; i < NGHTTP2_EXTPRI_URGENCY_LEVELS; ++i) { + if (!nghttp2_pq_empty(&session->sched[i].ob_data)) { + return 0; + } + } + + return 1; +} + +static void session_sched_reschedule_stream(nghttp2_session *session, + nghttp2_stream *stream) { + nghttp2_pq *pq; + uint32_t urgency = nghttp2_extpri_uint8_urgency(stream->extpri); + int inc = nghttp2_extpri_uint8_inc(stream->extpri); + uint64_t penalty = (uint64_t)stream->last_writelen; + int rv; + + (void)rv; + + assert(urgency < NGHTTP2_EXTPRI_URGENCY_LEVELS); + + pq = &session->sched[urgency].ob_data; + + if (!inc || nghttp2_pq_size(pq) == 1) { + return; + } + + nghttp2_pq_remove(pq, &stream->pq_entry); + + stream->cycle += penalty; + + rv = nghttp2_pq_push(pq, &stream->pq_entry); + + assert(0 == rv); +} + +static int session_update_stream_priority(nghttp2_session *session, + nghttp2_stream *stream, + uint8_t u8extpri) { + if (stream->extpri == u8extpri) { + return 0; + } + + if (stream->queued) { + session_ob_data_remove(session, stream); + + stream->extpri = u8extpri; + + return session_ob_data_push(session, stream); + } + + stream->extpri = u8extpri; + + return 0; +} + int nghttp2_session_add_item(nghttp2_session *session, nghttp2_outbound_item *item) { /* TODO Return error if stream is not found for the frame requiring @@ -857,7 +1127,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; @@ -953,6 +1223,18 @@ int nghttp2_session_add_rst_stream(nghttp2_session *session, int32_t stream_id, return 0; } + /* Sending RST_STREAM to an idle stream is subject to protocol + violation. Historically, nghttp2 allows this. In order not to + disrupt the existing applications, we don't error out this case + and simply ignore it. */ + if (nghttp2_session_is_my_stream_id(session, stream_id)) { + if ((uint32_t)stream_id >= session->next_stream_id) { + return 0; + } + } else if (session->last_recv_stream_id < stream_id) { + return 0; + } + /* Cancel pending request HEADERS in ob_syn if this RST_STREAM refers to that stream. */ if (!session->server && nghttp2_session_is_my_stream_id(session, stream_id) && @@ -963,8 +1245,7 @@ int nghttp2_session_add_rst_stream(nghttp2_session *session, int32_t stream_id, headers_frame = &nghttp2_outbound_queue_top(&session->ob_syn)->frame; assert(headers_frame->hd.type == NGHTTP2_HEADERS); - if (headers_frame->hd.stream_id <= stream_id && - (uint32_t)stream_id < session->next_stream_id) { + if (headers_frame->hd.stream_id <= stream_id) { for (item = session->ob_syn.head; item; item = item->qnext) { aux_data = &item->aux_data.headers; @@ -1022,13 +1303,27 @@ nghttp2_stream *nghttp2_session_open_stream(nghttp2_session *session, mem = &session->mem; stream = nghttp2_session_get_stream_raw(session, stream_id); + if (session->opt_flags & + NGHTTP2_OPTMASK_NO_RFC9113_LEADING_AND_TRAILING_WS_VALIDATION) { + flags |= NGHTTP2_STREAM_FLAG_NO_RFC9113_LEADING_AND_TRAILING_WS_VALIDATION; + } + 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_no_rfc7540_pri_no_fallback(session)) { + stream->flags |= NGHTTP2_STREAM_FLAG_NO_RFC7540_PRIORITIES; + } } } else { stream = nghttp2_mem_malloc(mem, sizeof(nghttp2_stream)); @@ -1039,7 +1334,21 @@ nghttp2_stream *nghttp2_session_open_stream(nghttp2_session *session, stream_alloc = 1; } - if (pri_spec->stream_id != 0) { + if (session_no_rfc7540_pri_no_fallback(session) || + 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 && @@ -1085,7 +1394,11 @@ nghttp2_stream *nghttp2_session_open_stream(nghttp2_session *session, (int32_t)session->local_settings.initial_window_size, stream_user_data, mem); - rv = nghttp2_map_insert(&session->streams, &stream->map_entry); + if (session_no_rfc7540_pri_no_fallback(session)) { + stream->seq = session->stream_seq++; + } + + rv = nghttp2_map_insert(&session->streams, stream_id, stream); if (rv != 0) { nghttp2_stream_free(stream); nghttp2_mem_free(mem, stream); @@ -1124,6 +1437,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; } @@ -1163,7 +1480,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; @@ -1213,6 +1530,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)) { @@ -1767,6 +2088,28 @@ static int session_predicate_origin_send(nghttp2_session *session) { return 0; } +static int session_predicate_priority_update_send(nghttp2_session *session, + int32_t stream_id) { + nghttp2_stream *stream; + + if (session_is_closing(session)) { + return NGHTTP2_ERR_SESSION_CLOSING; + } + + stream = nghttp2_session_get_stream(session, stream_id); + if (stream == NULL) { + return 0; + } + if (stream->state == NGHTTP2_STREAM_CLOSING) { + return NGHTTP2_ERR_STREAM_CLOSING; + } + if (stream->shut_flags & NGHTTP2_SHUT_RD) { + return NGHTTP2_ERR_INVALID_STREAM_STATE; + } + + return 0; +} + /* Take into account settings max frame size and both connection-level flow control here */ static ssize_t @@ -1996,7 +2339,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; @@ -2015,7 +2358,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)) { @@ -2034,7 +2377,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; @@ -2045,7 +2389,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; @@ -2061,7 +2405,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; @@ -2311,6 +2655,18 @@ static int session_prep_frame(nghttp2_session *session, } return 0; + case NGHTTP2_PRIORITY_UPDATE: { + nghttp2_ext_priority_update *priority_update = frame->ext.payload; + rv = session_predicate_priority_update_send(session, + priority_update->stream_id); + if (rv != 0) { + return rv; + } + + nghttp2_frame_pack_priority_update(&session->aob.framebufs, &frame->ext); + + return 0; + } default: /* Unreachable here */ assert(0); @@ -2322,6 +2678,8 @@ static int session_prep_frame(nghttp2_session *session, nghttp2_outbound_item * nghttp2_session_get_next_ob_item(nghttp2_session *session) { + nghttp2_outbound_item *item; + if (nghttp2_outbound_queue_top(&session->ob_urgent)) { return nghttp2_outbound_queue_top(&session->ob_urgent); } @@ -2337,7 +2695,12 @@ 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; + } + + return session_sched_get_next_outbound_item(session); } return NULL; @@ -2371,7 +2734,12 @@ 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; + } + + return session_sched_get_next_outbound_item(session); } return NULL; @@ -2407,7 +2775,7 @@ static int session_call_on_frame_send(nghttp2_session *session, return 0; } -static int find_stream_on_goaway_func(nghttp2_map_entry *entry, void *ptr) { +static int find_stream_on_goaway_func(void *entry, void *ptr) { nghttp2_close_stream_on_goaway_arg *arg; nghttp2_stream *stream; @@ -2481,10 +2849,20 @@ static int session_close_stream_on_goaway(nghttp2_session *session, return 0; } -static void reschedule_stream(nghttp2_stream *stream) { +static void session_reschedule_stream(nghttp2_session *session, + nghttp2_stream *stream) { stream->last_writelen = stream->item->frame.hd.length; - nghttp2_stream_reschedule(stream); + if (!(stream->flags & NGHTTP2_STREAM_FLAG_NO_RFC7540_PRIORITIES)) { + nghttp2_stream_reschedule(stream); + return; + } + + if (!session->server) { + return; + } + + session_sched_reschedule_stream(session, stream); } static int session_update_stream_consumed_size(nghttp2_session *session, @@ -2494,14 +2872,6 @@ static int session_update_stream_consumed_size(nghttp2_session *session, static int session_update_connection_consumed_size(nghttp2_session *session, size_t delta_size); -static int session_update_recv_connection_window_size(nghttp2_session *session, - size_t delta_size); - -static int session_update_recv_stream_window_size(nghttp2_session *session, - nghttp2_stream *stream, - size_t delta_size, - int send_window_update); - /* * Called after a frame is sent. This function runs * on_frame_send_callback and handles stream closure upon END_STREAM @@ -2541,7 +2911,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; } @@ -2666,9 +3036,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); @@ -2735,7 +3104,7 @@ static int session_after_frame_sent1(nghttp2_session *session) { if (session->opt_flags & NGHTTP2_OPTMASK_NO_AUTO_WINDOW_UPDATE) { rv = session_update_connection_consumed_size(session, 0); } else { - rv = session_update_recv_connection_window_size(session, 0); + rv = nghttp2_session_update_recv_connection_window_size(session, 0); } if (nghttp2_is_fatal(rv)) { @@ -2761,7 +3130,8 @@ static int session_after_frame_sent1(nghttp2_session *session) { if (session->opt_flags & NGHTTP2_OPTMASK_NO_AUTO_WINDOW_UPDATE) { rv = session_update_stream_consumed_size(session, stream, 0); } else { - rv = session_update_recv_stream_window_size(session, stream, 0, 1); + rv = + nghttp2_session_update_recv_stream_window_size(session, stream, 0, 1); } if (nghttp2_is_fatal(rv)) { @@ -2842,7 +3212,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; @@ -3140,7 +3510,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; @@ -3720,6 +4090,21 @@ static int session_end_stream_headers_received(nghttp2_session *session, nghttp2_frame *frame, nghttp2_stream *stream) { int rv; + + assert(frame->hd.type == NGHTTP2_HEADERS); + + if (session->server && session_enforce_http_messaging(session) && + frame->headers.cat == NGHTTP2_HCAT_REQUEST && + (stream->flags & NGHTTP2_STREAM_FLAG_NO_RFC7540_PRIORITIES) && + !(stream->flags & NGHTTP2_STREAM_FLAG_IGNORE_CLIENT_PRIORITIES) && + (stream->http_flags & NGHTTP2_HTTP_FLAG_PRIORITY)) { + rv = session_update_stream_priority(session, stream, stream->http_extpri); + if (rv != 0) { + assert(nghttp2_is_fatal(rv)); + return rv; + } + } + if ((frame->hd.flags & NGHTTP2_FLAG_END_STREAM) == 0) { return 0; } @@ -4081,6 +4466,8 @@ int nghttp2_session_on_priority_received(nghttp2_session *session, int rv; nghttp2_stream *stream; + assert(!session_no_rfc7540_pri_no_fallback(session)); + if (frame->hd.stream_id == 0) { return session_handle_invalid_connection(session, frame, NGHTTP2_ERR_PROTO, "PRIORITY: stream_id == 0"); @@ -4138,6 +4525,8 @@ static int session_process_priority_frame(nghttp2_session *session) { nghttp2_inbound_frame *iframe = &session->iframe; nghttp2_frame *frame = &iframe->frame; + assert(!session_no_rfc7540_pri_no_fallback(session)); + nghttp2_frame_unpack_priority_payload(&frame->priority, iframe->sbuf.pos); return nghttp2_session_on_priority_received(session, frame); @@ -4184,8 +4573,7 @@ static int session_process_rst_stream_frame(nghttp2_session *session) { return nghttp2_session_on_rst_stream_received(session, frame); } -static int update_remote_initial_window_size_func(nghttp2_map_entry *entry, - void *ptr) { +static int update_remote_initial_window_size_func(void *entry, void *ptr) { int rv; nghttp2_update_window_size_arg *arg; nghttp2_stream *stream; @@ -4205,8 +4593,8 @@ static int update_remote_initial_window_size_func(nghttp2_map_entry *entry, 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; @@ -4238,8 +4626,7 @@ session_update_remote_initial_window_size(nghttp2_session *session, update_remote_initial_window_size_func, &arg); } -static int update_local_initial_window_size_func(nghttp2_map_entry *entry, - void *ptr) { +static int update_local_initial_window_size_func(void *entry, void *ptr) { int rv; nghttp2_update_window_size_arg *arg; nghttp2_stream *stream; @@ -4251,9 +4638,16 @@ static int update_local_initial_window_size_func(nghttp2_map_entry *entry, return nghttp2_session_add_rst_stream(arg->session, stream->stream_id, NGHTTP2_FLOW_CONTROL_ERROR); } - if (!(arg->session->opt_flags & NGHTTP2_OPTMASK_NO_AUTO_WINDOW_UPDATE) && - stream->window_update_queued == 0 && - nghttp2_should_send_window_update(stream->local_window_size, + + if (stream->window_update_queued) { + return 0; + } + + if (arg->session->opt_flags & NGHTTP2_OPTMASK_NO_AUTO_WINDOW_UPDATE) { + return session_update_stream_consumed_size(arg->session, stream, 0); + } + + if (nghttp2_should_send_window_update(stream->local_window_size, stream->recv_window_size)) { rv = nghttp2_session_add_window_update(arg->session, NGHTTP2_FLAG_NONE, @@ -4374,6 +4768,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; } } @@ -4533,6 +4930,34 @@ 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 (session->server && session->pending_no_rfc7540_priorities && + (session->opt_flags & + NGHTTP2_OPTMASK_SERVER_FALLBACK_RFC7540_PRIORITIES)) { + session->fallback_rfc7540_priorities = 1; } } @@ -4818,8 +5243,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; @@ -4890,6 +5315,80 @@ int nghttp2_session_on_origin_received(nghttp2_session *session, return session_call_on_frame_received(session, frame); } +int nghttp2_session_on_priority_update_received(nghttp2_session *session, + nghttp2_frame *frame) { + nghttp2_ext_priority_update *priority_update; + nghttp2_stream *stream; + nghttp2_priority_spec pri_spec; + nghttp2_extpri extpri; + int rv; + + assert(session->server); + + priority_update = frame->ext.payload; + + if (frame->hd.stream_id != 0) { + return session_handle_invalid_connection(session, frame, NGHTTP2_ERR_PROTO, + "PRIORITY_UPDATE: stream_id == 0"); + } + + if (nghttp2_session_is_my_stream_id(session, priority_update->stream_id)) { + if (session_detect_idle_stream(session, priority_update->stream_id)) { + return session_handle_invalid_connection( + session, frame, NGHTTP2_ERR_PROTO, + "PRIORITY_UPDATE: prioritizing idle push is not allowed"); + } + + /* TODO Ignore priority signal to a push stream for now */ + return session_call_on_frame_received(session, frame); + } + + stream = nghttp2_session_get_stream_raw(session, priority_update->stream_id); + if (stream) { + /* Stream already exists. */ + if (stream->flags & NGHTTP2_STREAM_FLAG_IGNORE_CLIENT_PRIORITIES) { + return session_call_on_frame_received(session, frame); + } + } else if (session_detect_idle_stream(session, priority_update->stream_id)) { + if (session->num_idle_streams + session->num_incoming_streams >= + session->local_settings.max_concurrent_streams) { + return session_handle_invalid_connection( + session, frame, NGHTTP2_ERR_PROTO, + "PRIORITY_UPDATE: max concurrent streams exceeded"); + } + + nghttp2_priority_spec_default_init(&pri_spec); + stream = nghttp2_session_open_stream(session, priority_update->stream_id, + NGHTTP2_FLAG_NONE, &pri_spec, + NGHTTP2_STREAM_IDLE, NULL); + if (!stream) { + return NGHTTP2_ERR_NOMEM; + } + } else { + return session_call_on_frame_received(session, frame); + } + + extpri.urgency = NGHTTP2_EXTPRI_DEFAULT_URGENCY; + extpri.inc = 0; + + rv = nghttp2_http_parse_priority(&extpri, priority_update->field_value, + priority_update->field_value_len); + if (rv != 0) { + /* Just ignore field_value if it cannot be parsed. */ + return session_call_on_frame_received(session, frame); + } + + rv = session_update_stream_priority(session, stream, + nghttp2_extpri_to_uint8(&extpri)); + if (rv != 0) { + if (nghttp2_is_fatal(rv)) { + return rv; + } + } + + return session_call_on_frame_received(session, frame); +} + static int session_process_altsvc_frame(nghttp2_session *session) { nghttp2_inbound_frame *iframe = &session->iframe; nghttp2_frame *frame = &iframe->frame; @@ -4924,6 +5423,16 @@ static int session_process_origin_frame(nghttp2_session *session) { return nghttp2_session_on_origin_received(session, frame); } +static int session_process_priority_update_frame(nghttp2_session *session) { + nghttp2_inbound_frame *iframe = &session->iframe; + nghttp2_frame *frame = &iframe->frame; + + nghttp2_frame_unpack_priority_update_payload(&frame->ext, iframe->sbuf.pos, + nghttp2_buf_len(&iframe->sbuf)); + + return nghttp2_session_on_priority_update_received(session, frame); +} + static int session_process_extension_frame(nghttp2_session *session) { int rv; nghttp2_inbound_frame *iframe = &session->iframe; @@ -5019,22 +5528,10 @@ static int adjust_recv_window_size(int32_t *recv_window_size_ptr, size_t delta, return 0; } -/* - * Accumulates received bytes |delta_size| for stream-level flow - * control and decides whether to send WINDOW_UPDATE to that stream. - * If NGHTTP2_OPT_NO_AUTO_WINDOW_UPDATE is set, WINDOW_UPDATE will not - * be sent. - * - * This function returns 0 if it succeeds, or one of the following - * negative error codes: - * - * NGHTTP2_ERR_NOMEM - * Out of memory. - */ -static int session_update_recv_stream_window_size(nghttp2_session *session, - nghttp2_stream *stream, - size_t delta_size, - int send_window_update) { +int nghttp2_session_update_recv_stream_window_size(nghttp2_session *session, + nghttp2_stream *stream, + size_t delta_size, + int send_window_update) { int rv; rv = adjust_recv_window_size(&stream->recv_window_size, delta_size, stream->local_window_size); @@ -5063,20 +5560,8 @@ static int session_update_recv_stream_window_size(nghttp2_session *session, return 0; } -/* - * Accumulates received bytes |delta_size| for connection-level flow - * control and decides whether to send WINDOW_UPDATE to the - * connection. If NGHTTP2_OPT_NO_AUTO_WINDOW_UPDATE is set, - * WINDOW_UPDATE will not be sent. - * - * This function returns 0 if it succeeds, or one of the following - * negative error codes: - * - * NGHTTP2_ERR_NOMEM - * Out of memory. - */ -static int session_update_recv_connection_window_size(nghttp2_session *session, - size_t delta_size) { +int nghttp2_session_update_recv_connection_window_size(nghttp2_session *session, + size_t delta_size) { int rv; rv = adjust_recv_window_size(&session->recv_window_size, delta_size, session->local_window_size); @@ -5285,6 +5770,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); @@ -5357,7 +5843,7 @@ static ssize_t inbound_frame_compute_pad(nghttp2_inbound_frame *iframe) { /* * This function returns the effective payload length in the data of - * length |readlen| when the remaning payload is |payloadleft|. The + * length |readlen| when the remaining payload is |payloadleft|. The * |payloadleft| does not include |readlen|. If padding was started * strictly before this data chunk, this function returns -1. */ @@ -5378,9 +5864,11 @@ static ssize_t inbound_frame_effective_readlen(nghttp2_inbound_frame *iframe, return (ssize_t)(readlen); } +static const uint8_t static_in[] = {0}; + ssize_t nghttp2_session_mem_recv(nghttp2_session *session, const uint8_t *in, size_t inlen) { - const uint8_t *first = in, *last = in + inlen; + const uint8_t *first, *last; nghttp2_inbound_frame *iframe = &session->iframe; size_t readlen; ssize_t padlen; @@ -5391,6 +5879,14 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session, const uint8_t *in, size_t pri_fieldlen; nghttp2_mem *mem; + if (in == NULL) { + assert(inlen == 0); + in = static_in; + } + + first = in; + last = in + inlen; + DEBUGF("recv: connection recv_window_size=%d, local_window=%d\n", session->recv_window_size, session->local_window_size); @@ -5678,6 +6174,12 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session, const uint8_t *in, break; } + /* Check the settings flood counter early to be safe */ + if (session->obq_flood_counter_ >= session->max_outbound_ack && + !(iframe->frame.hd.flags & NGHTTP2_FLAG_ACK)) { + return NGHTTP2_ERR_FLOODED; + } + iframe->state = NGHTTP2_IB_READ_SETTINGS; if (iframe->payloadleft) { @@ -5688,6 +6190,16 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session, const uint8_t *in, iframe->max_niv = iframe->frame.hd.length / NGHTTP2_FRAME_SETTINGS_ENTRY_LENGTH + 1; + if (iframe->max_niv - 1 > session->max_settings) { + rv = nghttp2_session_terminate_session_with_reason( + session, NGHTTP2_ENHANCE_YOUR_CALM, + "SETTINGS: too many setting entries"); + if (nghttp2_is_fatal(rv)) { + return rv; + } + return (ssize_t)inlen; + } + iframe->iv = nghttp2_mem_malloc(mem, sizeof(nghttp2_settings_entry) * iframe->max_niv); @@ -5873,6 +6385,49 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session, const uint8_t *in, iframe->state = NGHTTP2_IB_READ_ORIGIN_PAYLOAD; break; + case NGHTTP2_PRIORITY_UPDATE: + if ((session->builtin_recv_ext_types & + NGHTTP2_TYPEMASK_PRIORITY_UPDATE) == 0) { + busy = 1; + iframe->state = NGHTTP2_IB_IGN_PAYLOAD; + break; + } + + DEBUGF("recv: PRIORITY_UPDATE\n"); + + iframe->frame.hd.flags = NGHTTP2_FLAG_NONE; + iframe->frame.ext.payload = + &iframe->ext_frame_payload.priority_update; + + if (!session->server) { + rv = nghttp2_session_terminate_session_with_reason( + session, NGHTTP2_PROTOCOL_ERROR, + "PRIORITY_UPDATE is received from server"); + if (nghttp2_is_fatal(rv)) { + return rv; + } + return (ssize_t)inlen; + } + + if (iframe->payloadleft < 4) { + busy = 1; + iframe->state = NGHTTP2_IB_FRAME_SIZE_ERROR; + break; + } + + if (!session_no_rfc7540_pri_no_fallback(session) || + iframe->payloadleft > sizeof(iframe->raw_sbuf)) { + busy = 1; + iframe->state = NGHTTP2_IB_IGN_PAYLOAD; + break; + } + + busy = 1; + + iframe->state = NGHTTP2_IB_READ_NBYTE; + inbound_frame_set_mark(iframe, iframe->payloadleft); + + break; default: busy = 1; @@ -5978,13 +6533,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_no_rfc7540_pri_no_fallback(session) && + 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); @@ -6141,6 +6699,18 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session, const uint8_t *in, iframe->state = NGHTTP2_IB_READ_ALTSVC_PAYLOAD; break; + case NGHTTP2_PRIORITY_UPDATE: + DEBUGF("recv: prioritized_stream_id=%d\n", + nghttp2_get_uint32(iframe->sbuf.pos) & NGHTTP2_STREAM_ID_MASK); + + rv = session_process_priority_update_frame(session); + if (nghttp2_is_fatal(rv)) { + return rv; + } + + session_inbound_frame_reset(session); + + break; } default: /* This is unknown frame */ @@ -6420,8 +6990,9 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session, const uint8_t *in, /* CONTINUATION won't bear NGHTTP2_PADDED flag */ - iframe->frame.hd.flags = (uint8_t)( - iframe->frame.hd.flags | (cont_hd.flags & NGHTTP2_FLAG_END_HEADERS)); + iframe->frame.hd.flags = + (uint8_t)(iframe->frame.hd.flags | + (cont_hd.flags & NGHTTP2_FLAG_END_HEADERS)); iframe->frame.hd.length += cont_hd.length; busy = 1; @@ -6454,7 +7025,7 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session, const uint8_t *in, } /* Pad Length field is subject to flow control */ - rv = session_update_recv_connection_window_size(session, readlen); + rv = nghttp2_session_update_recv_connection_window_size(session, readlen); if (nghttp2_is_fatal(rv)) { return rv; } @@ -6477,7 +7048,7 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session, const uint8_t *in, stream = nghttp2_session_get_stream(session, iframe->frame.hd.stream_id); if (stream) { - rv = session_update_recv_stream_window_size( + rv = nghttp2_session_update_recv_stream_window_size( session, stream, readlen, iframe->payloadleft || (iframe->frame.hd.flags & NGHTTP2_FLAG_END_STREAM) == 0); @@ -6524,7 +7095,8 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session, const uint8_t *in, if (readlen > 0) { ssize_t data_readlen; - rv = session_update_recv_connection_window_size(session, readlen); + rv = nghttp2_session_update_recv_connection_window_size(session, + readlen); if (nghttp2_is_fatal(rv)) { return rv; } @@ -6533,7 +7105,7 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session, const uint8_t *in, return (ssize_t)inlen; } - rv = session_update_recv_stream_window_size( + rv = nghttp2_session_update_recv_stream_window_size( session, stream, readlen, iframe->payloadleft || (iframe->frame.hd.flags & NGHTTP2_FLAG_END_STREAM) == 0); @@ -6634,7 +7206,8 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session, const uint8_t *in, if (readlen > 0) { /* Update connection-level flow control window for ignored DATA frame too */ - rv = session_update_recv_connection_window_size(session, readlen); + rv = nghttp2_session_update_recv_connection_window_size(session, + readlen); if (nghttp2_is_fatal(rv)) { return rv; } @@ -6850,7 +7423,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) || + !session_sched_empty(session)) && session->remote_window_size > 0) || (nghttp2_outbound_queue_top(&session->ob_syn) && !session_is_outgoing_concurrent_streams_max(session)); @@ -7003,6 +7577,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; @@ -7020,6 +7595,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; @@ -7094,6 +7684,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; } @@ -7222,7 +7818,7 @@ int nghttp2_session_pack_data(nghttp2_session *session, nghttp2_bufs *bufs, return rv; } - reschedule_stream(stream); + session_reschedule_stream(session, stream); if (frame->hd.length == 0 && (data_flags & NGHTTP2_DATA_FLAG_EOF) && (data_flags & NGHTTP2_DATA_FLAG_NO_END_STREAM)) { @@ -7296,7 +7892,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)) { @@ -7404,6 +8000,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); @@ -7427,6 +8025,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); @@ -7454,6 +8054,11 @@ static int nghttp2_session_upgrade_internal(nghttp2_session *session, if (settings_payloadlen % NGHTTP2_FRAME_SETTINGS_ENTRY_LENGTH) { return NGHTTP2_ERR_INVALID_ARGUMENT; } + /* SETTINGS frame contains too many settings */ + if (settings_payloadlen / NGHTTP2_FRAME_SETTINGS_ENTRY_LENGTH > + session->max_settings) { + return NGHTTP2_ERR_TOO_MANY_SETTINGS; + } rv = nghttp2_frame_unpack_settings_payload2(&iv, &niv, settings_payload, settings_payloadlen, mem); if (rv != 0) { @@ -7705,6 +8310,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; } @@ -7737,6 +8346,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; @@ -7778,3 +8391,38 @@ nghttp2_session_get_hd_deflate_dynamic_table_size(nghttp2_session *session) { void nghttp2_session_set_user_data(nghttp2_session *session, void *user_data) { session->user_data = user_data; } + +int nghttp2_session_change_extpri_stream_priority( + nghttp2_session *session, int32_t stream_id, + const nghttp2_extpri *extpri_in, int ignore_client_signal) { + nghttp2_stream *stream; + nghttp2_extpri extpri = *extpri_in; + + if (!session->server) { + return NGHTTP2_ERR_INVALID_STATE; + } + + if (session->pending_no_rfc7540_priorities != 1) { + return 0; + } + + if (stream_id == 0) { + return NGHTTP2_ERR_INVALID_ARGUMENT; + } + + stream = nghttp2_session_get_stream_raw(session, stream_id); + if (!stream) { + return NGHTTP2_ERR_INVALID_ARGUMENT; + } + + if (extpri.urgency > NGHTTP2_EXTPRI_URGENCY_LOW) { + extpri.urgency = NGHTTP2_EXTPRI_URGENCY_LOW; + } + + if (ignore_client_signal) { + stream->flags |= NGHTTP2_STREAM_FLAG_IGNORE_CLIENT_PRIORITIES; + } + + return session_update_stream_priority(session, stream, + nghttp2_extpri_to_uint8(&extpri)); +} |