From 0bcf90d32d89020ae1f0dc5fef0cb8c654d7beb1 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Tue, 23 Jul 2013 00:11:39 +0900 Subject: [PATCH] Support PRIORITY frame send and receive --- lib/includes/nghttp2/nghttp2.h | 17 ++++++ lib/nghttp2_session.c | 97 ++++++++++++++++++++++++++++++++++ lib/nghttp2_session.h | 14 +++++ lib/nghttp2_submit.c | 22 ++++++++ tests/main.c | 5 ++ tests/nghttp2_session_test.c | 71 +++++++++++++++++++++++++ tests/nghttp2_session_test.h | 3 ++ 7 files changed, 229 insertions(+) diff --git a/lib/includes/nghttp2/nghttp2.h b/lib/includes/nghttp2/nghttp2.h index 3b0eb87e..45b63b78 100644 --- a/lib/includes/nghttp2/nghttp2.h +++ b/lib/includes/nghttp2/nghttp2.h @@ -1472,6 +1472,23 @@ int nghttp2_submit_data(nghttp2_session *session, uint8_t flags, int32_t stream_id, const nghttp2_data_provider *data_prd); +/** + * @function + * + * Submits PRIORITY frame to change the priority of stream |stream_id| + * to the priority value |pri|. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * :enum:`NGHTTP2_ERR_NOMEM` + * Out of memory. + * :enum:`NGHTTP2_ERR_INVALID_ARGUMENT` + * The |pri| is negative. + */ +int nghttp2_submit_priority(nghttp2_session *session, int32_t stream_id, + int32_t pri); + /** * @function * diff --git a/lib/nghttp2_session.c b/lib/nghttp2_session.c index b6946cc3..c5908f53 100644 --- a/lib/nghttp2_session.c +++ b/lib/nghttp2_session.c @@ -333,6 +333,9 @@ int nghttp2_session_add_frame(nghttp2_session *session, } } break; + case NGHTTP2_PRIORITY: + item->pri = -1; + break; case NGHTTP2_RST_STREAM: { nghttp2_stream *stream; stream = nghttp2_session_get_stream(session, frame->hd.stream_id); @@ -617,6 +620,40 @@ static int nghttp2_session_predicate_headers_send(nghttp2_session *session, } } +/* + * This function checks PRIORITY frame with stream ID |stream_id| can + * be sent at this time. + * + * This function returns 0 if it is succeeds, or one of the following + * negative error codes: + * + * NGHTTP2_ERR_STREAM_CLOSED + * The stream is already closed or does not exist. + * NGHTTP2_ERR_STREAM_SHUT_WR + * The transmission is not allowed for this stream (e.g., a frame + * with END_STREAM flag set has already sent) + * NGHTTP2_ERR_STREAM_CLOSING + * RST_STREAM was queued for this stream. + */ +static int nghttp2_session_predicate_priority_send +(nghttp2_session *session, int32_t stream_id) +{ + nghttp2_stream *stream = nghttp2_session_get_stream(session, stream_id); + int r; + r = nghttp2_predicate_stream_for_send(stream); + if(r != 0) { + return r; + } + /* The spec is not clear if the receiving side can issue PRIORITY + and the other side should do when receiving it. We just send + PRIORITY if requested. */ + if(stream->state != NGHTTP2_STREAM_CLOSING) { + return 0; + } else { + return NGHTTP2_ERR_STREAM_CLOSING; + } +} + /* * This function checks WINDOW_UPDATE with the stream ID |stream_id| * can be sent at this time. Note that FIN flag of the previous frame @@ -790,6 +827,21 @@ static ssize_t nghttp2_session_prep_frame(nghttp2_session *session, } } break; + case NGHTTP2_PRIORITY: { + int r; + r = nghttp2_session_predicate_priority_send + (session, frame->hd.stream_id); + if(r != 0) { + return r; + } + framebuflen = nghttp2_frame_pack_priority(&session->aob.framebuf, + &session->aob.framebufmax, + &frame->priority); + if(framebuflen < 0) { + return framebuflen; + } + break; + } case NGHTTP2_RST_STREAM: framebuflen = nghttp2_frame_pack_rst_stream(&session->aob.framebuf, &session->aob.framebufmax, @@ -1076,6 +1128,15 @@ static int nghttp2_session_after_frame_sent(nghttp2_session *session) } break; } + case NGHTTP2_PRIORITY: { + nghttp2_stream *stream = nghttp2_session_get_stream(session, + frame->hd.stream_id); + if(stream) { + // Just update priority for the stream for now. + stream->pri = frame->priority.pri; + } + break; + } case NGHTTP2_RST_STREAM: nghttp2_session_close_stream(session, frame->hd.stream_id, frame->rst_stream.error_code); @@ -1556,6 +1617,28 @@ int nghttp2_session_on_headers_received(nghttp2_session *session, return r; } +int nghttp2_session_on_priority_received(nghttp2_session *session, + nghttp2_frame *frame) +{ + nghttp2_stream *stream; + if(frame->hd.stream_id == 0) { + return nghttp2_session_handle_invalid_connection(session, frame, + NGHTTP2_PROTOCOL_ERROR); + } + stream = nghttp2_session_get_stream(session, frame->hd.stream_id); + if(stream) { + if((stream->shut_flags & NGHTTP2_SHUT_RD) == 0) { + // Just update priority anyway for now + stream->pri = frame->priority.pri; + nghttp2_session_call_on_frame_received(session, frame); + } else { + return nghttp2_session_handle_invalid_stream(session, frame, + NGHTTP2_PROTOCOL_ERROR); + } + } + return 0; +} + int nghttp2_session_on_rst_stream_received(nghttp2_session *session, nghttp2_frame *frame) { @@ -1951,6 +2034,20 @@ static int nghttp2_session_process_ctrl_frame(nghttp2_session *session) NGHTTP2_PROTOCOL_ERROR); } break; + case NGHTTP2_PRIORITY: + r = nghttp2_frame_unpack_priority(&frame.priority, + session->iframe.headbuf, + sizeof(session->iframe.headbuf), + session->iframe.buf, + session->iframe.buflen); + if(r == 0) { + r = nghttp2_session_on_priority_received(session, &frame); + nghttp2_frame_priority_free(&frame.priority); + } else if(nghttp2_is_non_fatal(r)) { + r = nghttp2_session_handle_parse_error(session, type, r, + NGHTTP2_PROTOCOL_ERROR); + } + break; case NGHTTP2_RST_STREAM: r = nghttp2_frame_unpack_rst_stream(&frame.rst_stream, session->iframe.headbuf, diff --git a/lib/nghttp2_session.h b/lib/nghttp2_session.h index b84aaa2b..80c2d019 100644 --- a/lib/nghttp2_session.h +++ b/lib/nghttp2_session.h @@ -360,6 +360,20 @@ int nghttp2_session_on_headers_received(nghttp2_session *session, nghttp2_frame *frame, nghttp2_stream *stream); + +/* + * Called when PRIORITY is received, assuming |frame| is properly + * initialized. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * NGHTTP2_ERR_NOMEM + * Out of memory. + */ +int nghttp2_session_on_priority_received(nghttp2_session *session, + nghttp2_frame *frame); + /* * Called when RST_STREAM is received, assuming |frame| is properly * initialized. diff --git a/lib/nghttp2_submit.c b/lib/nghttp2_submit.c index b962ccc1..d52a089d 100644 --- a/lib/nghttp2_submit.c +++ b/lib/nghttp2_submit.c @@ -112,6 +112,28 @@ int nghttp2_submit_ping(nghttp2_session *session, uint8_t *opaque_data) return nghttp2_session_add_ping(session, NGHTTP2_FLAG_NONE, opaque_data); } +int nghttp2_submit_priority(nghttp2_session *session, int32_t stream_id, + int32_t pri) +{ + int r; + nghttp2_frame *frame; + if(pri < 0) { + return NGHTTP2_ERR_INVALID_ARGUMENT; + } + frame = malloc(sizeof(nghttp2_frame)); + if(frame == NULL) { + return NGHTTP2_ERR_NOMEM; + } + nghttp2_frame_priority_init(&frame->priority, stream_id, pri); + r = nghttp2_session_add_frame(session, NGHTTP2_CAT_CTRL, frame, NULL); + if(r != 0) { + nghttp2_frame_priority_free(&frame->priority); + free(frame); + return r; + } + return 0; +} + int nghttp2_submit_rst_stream(nghttp2_session *session, int32_t stream_id, nghttp2_error_code error_code) { diff --git a/tests/main.c b/tests/main.c index dfc99d50..62da0b2d 100644 --- a/tests/main.c +++ b/tests/main.c @@ -90,6 +90,8 @@ int main(int argc, char* argv[]) test_nghttp2_session_on_syn_reply_received) || !CU_add_test(pSuite, "session_on_headers_received", test_nghttp2_session_on_headers_received) || + !CU_add_test(pSuite, "session_on_priority_received", + test_nghttp2_session_on_priority_received) || !CU_add_test(pSuite, "session_on_rst_stream_received", test_nghttp2_session_on_rst_stream_received) || !CU_add_test(pSuite, "session_on_settings_received", @@ -106,6 +108,8 @@ int main(int argc, char* argv[]) test_nghttp2_session_send_headers_start_stream) || !CU_add_test(pSuite, "session_send_headers_reply", test_nghttp2_session_send_headers_reply) || + !CU_add_test(pSuite, "session_send_priority", + test_nghttp2_session_send_priority) || !CU_add_test(pSuite, "session_send_rst_stream", test_nghttp2_session_send_rst_stream) || !CU_add_test(pSuite, "session_is_my_stream_id", @@ -122,6 +126,7 @@ int main(int argc, char* argv[]) !CU_add_test(pSuite, "submit_headers_reply", test_nghttp2_submit_headers_reply) || !CU_add_test(pSuite, "submit_headers", test_nghttp2_submit_headers) || + !CU_add_test(pSuite, "submit_priority", test_nghttp2_submit_priority) || !CU_add_test(pSuite, "session_submit_settings", test_nghttp2_submit_settings) || !CU_add_test(pSuite, "submit_window_update", diff --git a/tests/nghttp2_session_test.c b/tests/nghttp2_session_test.c index 397ac19a..cbef076a 100644 --- a/tests/nghttp2_session_test.c +++ b/tests/nghttp2_session_test.c @@ -784,6 +784,27 @@ void test_nghttp2_session_on_headers_received(void) nghttp2_session_del(session); } +void test_nghttp2_session_on_priority_received(void) +{ + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + my_user_data user_data; + nghttp2_frame frame; + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + nghttp2_session_server_new(&session, &callbacks, &user_data); + nghttp2_session_open_stream(session, 1, NGHTTP2_FLAG_NONE, + NGHTTP2_PRI_DEFAULT, + NGHTTP2_STREAM_OPENING, NULL); + + nghttp2_frame_priority_init(&frame.priority, 1, 1000000007); + + CU_ASSERT(0 == nghttp2_session_on_priority_received(session, &frame)); + CU_ASSERT(1000000007 == nghttp2_session_get_stream(session, 1)->pri); + + nghttp2_frame_priority_free(&frame.priority); + nghttp2_session_del(session); +} + void test_nghttp2_session_on_rst_stream_received(void) { nghttp2_session *session; @@ -1094,6 +1115,29 @@ void test_nghttp2_session_send_headers_reply(void) nghttp2_session_del(session); } +void test_nghttp2_session_send_priority(void) +{ + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + my_user_data user_data; + nghttp2_frame *frame; + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + nghttp2_session_client_new(&session, &callbacks, &user_data); + nghttp2_session_open_stream(session, 1, NGHTTP2_FLAG_NONE, + NGHTTP2_PRI_DEFAULT, + NGHTTP2_STREAM_OPENING, NULL); + + frame = malloc(sizeof(nghttp2_frame)); + nghttp2_frame_priority_init(&frame->priority, 1, 1000000007); + nghttp2_session_add_frame(session, NGHTTP2_CAT_CTRL, frame, NULL); + CU_ASSERT(0 == nghttp2_session_send(session)); + + CU_ASSERT(1000000007 == nghttp2_session_get_stream(session, 1)->pri); + + nghttp2_session_del(session); +} + void test_nghttp2_session_send_rst_stream(void) { nghttp2_session *session; @@ -1382,6 +1426,25 @@ void test_nghttp2_submit_headers(void) nghttp2_session_del(session); } +void test_nghttp2_submit_priority(void) +{ + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + my_user_data ud; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + callbacks.on_frame_send_callback = on_frame_send_callback; + nghttp2_session_server_new(&session, &callbacks, &ud); + + CU_ASSERT(NGHTTP2_ERR_INVALID_ARGUMENT == + nghttp2_submit_priority(session, 1, -1)); + + CU_ASSERT(0 == nghttp2_submit_priority(session, 1, 1000000007)); + + nghttp2_session_del(session); +} + void test_nghttp2_submit_settings(void) { nghttp2_session *session; @@ -2222,6 +2285,14 @@ void test_nghttp2_session_on_ctrl_not_send(void) CU_ASSERT(NGHTTP2_ERR_STREAM_ID_NOT_AVAILABLE == user_data.not_sent_error); session->next_stream_id = 1; + user_data.frame_not_send_cb_called = 0; + /* Send PRIORITY to stream ID = 1 which does not exist */ + CU_ASSERT(0 == nghttp2_submit_priority(session, 1, 0)); + CU_ASSERT(0 == nghttp2_session_send(session)); + CU_ASSERT(1 == user_data.frame_not_send_cb_called); + CU_ASSERT(NGHTTP2_PRIORITY == user_data.not_sent_frame_type); + CU_ASSERT(NGHTTP2_ERR_STREAM_CLOSED == user_data.not_sent_error); + user_data.frame_not_send_cb_called = 0; /* Send GOAWAY */ CU_ASSERT(0 == nghttp2_submit_goaway(session, NGHTTP2_NO_ERROR, NULL, 0)); diff --git a/tests/nghttp2_session_test.h b/tests/nghttp2_session_test.h index a64149ff..a71ba9f8 100644 --- a/tests/nghttp2_session_test.h +++ b/tests/nghttp2_session_test.h @@ -34,6 +34,7 @@ void test_nghttp2_session_add_frame(void); void test_nghttp2_session_on_syn_stream_received(void); void test_nghttp2_session_on_syn_reply_received(void); void test_nghttp2_session_on_headers_received(void); +void test_nghttp2_session_on_priority_received(void); void test_nghttp2_session_on_rst_stream_received(void); void test_nghttp2_session_on_settings_received(void); void test_nghttp2_session_on_ping_received(void); @@ -42,6 +43,7 @@ void test_nghttp2_session_on_window_update_received(void); void test_nghttp2_session_on_data_received(void); void test_nghttp2_session_send_headers_start_stream(void); void test_nghttp2_session_send_headers_reply(void); +void test_nghttp2_session_send_priority(void); void test_nghttp2_session_send_rst_stream(void); void test_nghttp2_session_is_my_stream_id(void); void test_nghttp2_submit_response(void); @@ -51,6 +53,7 @@ void test_nghttp2_submit_request_without_data(void); void test_nghttp2_submit_headers_start_stream(void); void test_nghttp2_submit_headers_reply(void); void test_nghttp2_submit_headers(void); +void test_nghttp2_submit_priority(void); void test_nghttp2_submit_settings(void); void test_nghttp2_submit_window_update(void); void test_nghttp2_submit_invalid_nv(void);