diff --git a/doc/Makefile.am b/doc/Makefile.am index ba5b7888..a1a177e8 100644 --- a/doc/Makefile.am +++ b/doc/Makefile.am @@ -107,6 +107,7 @@ APIDOCS= \ nghttp2_session_callbacks_set_send_callback.rst \ nghttp2_session_callbacks_set_send_data_callback.rst \ nghttp2_session_callbacks_set_unpack_extension_callback.rst \ + nghttp2_session_change_extpri_stream_priority.rst \ nghttp2_session_change_stream_priority.rst \ nghttp2_session_check_request_allowed.rst \ nghttp2_session_check_server_session.rst \ diff --git a/lib/includes/nghttp2/nghttp2.h b/lib/includes/nghttp2/nghttp2.h index effed7ff..2e87c481 100644 --- a/lib/includes/nghttp2/nghttp2.h +++ b/lib/includes/nghttp2/nghttp2.h @@ -4901,6 +4901,40 @@ NGHTTP2_EXTERN int nghttp2_submit_priority_update(nghttp2_session *session, const uint8_t *field_value, size_t field_value_len); +/** + * @function + * + * Changes the priority of the existing stream denoted by |stream_id|. + * The new priority is |extpri|. This function is meant to be used by + * server for :rfc:`9218` extensible prioritization scheme. + * + * If |session| is initialized as client, this function returns + * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_STATE`. For client, use + * `nghttp2_submit_priority_update()` instead. + * + * If :member:`extpri->urgency ` is out of + * bound, it is set to :macro:`NGHTTP2_EXTPRI_URGENCY_LOW`. + * + * If |ignore_client_signal| is nonzero, server starts to ignore + * client priority signals for this stream. + * + * If + * :enum:`nghttp2_settings_id.NGHTTP2_SETTINGS_NO_RFC7540_PRIORITIES` + * of value of 1 is not submitted via `nghttp2_submit_settings()`, + * this function does nothing and returns 0. + * + * :enum:`nghttp2_error.NGHTTP2_ERR_NOMEM` + * Out of memory. + * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_STATE` + * The |session| is initialized as client. + * :enum:`nghttp2_error.NGHTTP2_ERR_INVALID_ARGUMENT` + * |stream_id| is zero; or a stream denoted by |stream_id| is not + * found. + */ +NGHTTP2_EXTERN int nghttp2_session_change_extpri_stream_priority( + nghttp2_session *session, int32_t stream_id, const nghttp2_extpri *extpri, + int ignore_client_signal); + /** * @function * diff --git a/lib/nghttp2_session.c b/lib/nghttp2_session.c index dc8eb349..134b0efb 100644 --- a/lib/nghttp2_session.c +++ b/lib/nghttp2_session.c @@ -4084,6 +4084,7 @@ static int session_end_stream_headers_received(nghttp2_session *session, 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) { @@ -5333,6 +5334,9 @@ int nghttp2_session_on_priority_update_received(nghttp2_session *session, 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) { @@ -8375,3 +8379,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)); +} diff --git a/lib/nghttp2_stream.h b/lib/nghttp2_stream.h index 4f5585e8..0ec77bf0 100644 --- a/lib/nghttp2_stream.h +++ b/lib/nghttp2_stream.h @@ -94,6 +94,8 @@ typedef enum { /* Indicates that this stream is not subject to RFC7540 priorities scheme. */ NGHTTP2_STREAM_FLAG_NO_RFC7540_PRIORITIES = 0x10, + /* Ignore client RFC 9218 priority signal. */ + NGHTTP2_STREAM_FLAG_IGNORE_CLIENT_PRIORITIES = 0x20, } nghttp2_stream_flag; /* HTTP related flags to enforce HTTP semantics */ diff --git a/tests/main.c b/tests/main.c index 92ce6679..ed26a80a 100644 --- a/tests/main.c +++ b/tests/main.c @@ -317,6 +317,8 @@ int main(void) { !CU_add_test(pSuite, "session_flooding", test_nghttp2_session_flooding) || !CU_add_test(pSuite, "session_change_stream_priority", test_nghttp2_session_change_stream_priority) || + !CU_add_test(pSuite, "session_change_extpri_stream_priority", + test_nghttp2_session_change_extpri_stream_priority) || !CU_add_test(pSuite, "session_create_idle_stream", test_nghttp2_session_create_idle_stream) || !CU_add_test(pSuite, "session_repeated_priority_change", diff --git a/tests/nghttp2_session_test.c b/tests/nghttp2_session_test.c index cf5a775d..be13f5f4 100644 --- a/tests/nghttp2_session_test.c +++ b/tests/nghttp2_session_test.c @@ -10698,6 +10698,84 @@ void test_nghttp2_session_change_stream_priority(void) { nghttp2_session_del(session); } +void test_nghttp2_session_change_extpri_stream_priority(void) { + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_bufs bufs; + nghttp2_buf *buf; + ssize_t rv; + nghttp2_option *option; + nghttp2_extension frame; + nghttp2_ext_priority_update priority_update; + nghttp2_extpri extpri; + nghttp2_stream *stream; + static const uint8_t field_value[] = "u=2"; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + + frame_pack_bufs_init(&bufs); + + nghttp2_option_new(&option); + nghttp2_option_set_builtin_recv_extension_type(option, + NGHTTP2_PRIORITY_UPDATE); + + nghttp2_session_server_new2(&session, &callbacks, NULL, option); + + session->pending_no_rfc7540_priorities = 1; + + open_recv_stream(session, 1); + + extpri.urgency = NGHTTP2_EXTPRI_URGENCY_LOW + 1; + extpri.inc = 1; + + rv = nghttp2_session_change_extpri_stream_priority( + session, 1, &extpri, /* ignore_client_signal = */ 0); + + CU_ASSERT(0 == rv); + + stream = nghttp2_session_get_stream(session, 1); + + CU_ASSERT(NGHTTP2_EXTPRI_URGENCY_LOW == + nghttp2_extpri_uint8_urgency(stream->extpri)); + CU_ASSERT(1 == nghttp2_extpri_uint8_inc(stream->extpri)); + + /* Client can still update stream priority. */ + frame.payload = &priority_update; + nghttp2_frame_priority_update_init(&frame, 1, (uint8_t *)field_value, + sizeof(field_value) - 1); + nghttp2_frame_pack_priority_update(&bufs, &frame); + + buf = &bufs.head->buf; + rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(buf) == rv); + CU_ASSERT(2 == stream->extpri); + + /* Start to ignore client priority signal for this stream. */ + rv = nghttp2_session_change_extpri_stream_priority( + session, 1, &extpri, /* ignore_client_signal = */ 1); + + CU_ASSERT(0 == rv); + + stream = nghttp2_session_get_stream(session, 1); + + CU_ASSERT(NGHTTP2_EXTPRI_URGENCY_LOW == + nghttp2_extpri_uint8_urgency(stream->extpri)); + CU_ASSERT(1 == nghttp2_extpri_uint8_inc(stream->extpri)); + + buf = &bufs.head->buf; + rv = nghttp2_session_mem_recv(session, buf->pos, nghttp2_buf_len(buf)); + + CU_ASSERT((ssize_t)nghttp2_buf_len(buf) == rv); + CU_ASSERT(NGHTTP2_EXTPRI_URGENCY_LOW == + nghttp2_extpri_uint8_urgency(stream->extpri)); + CU_ASSERT(1 == nghttp2_extpri_uint8_inc(stream->extpri)); + + nghttp2_session_del(session); + nghttp2_option_del(option); + nghttp2_bufs_free(&bufs); +} + void test_nghttp2_session_create_idle_stream(void) { nghttp2_session *session; nghttp2_session_callbacks callbacks; diff --git a/tests/nghttp2_session_test.h b/tests/nghttp2_session_test.h index a46d0220..e2710e34 100644 --- a/tests/nghttp2_session_test.h +++ b/tests/nghttp2_session_test.h @@ -155,6 +155,7 @@ void test_nghttp2_session_defer_then_close(void); void test_nghttp2_session_detach_item_from_closed_stream(void); void test_nghttp2_session_flooding(void); void test_nghttp2_session_change_stream_priority(void); +void test_nghttp2_session_change_extpri_stream_priority(void); void test_nghttp2_session_create_idle_stream(void); void test_nghttp2_session_repeated_priority_change(void); void test_nghttp2_session_repeated_priority_submission(void);