diff --git a/lib/nghttp2_session.c b/lib/nghttp2_session.c index bbdd0fe4..8795b1bd 100644 --- a/lib/nghttp2_session.c +++ b/lib/nghttp2_session.c @@ -198,7 +198,8 @@ nghttp2_stream* nghttp2_session_get_stream(nghttp2_session *session, stream = (nghttp2_stream*)nghttp2_map_find(&session->streams, stream_id); - if(stream == NULL || (stream->flags & NGHTTP2_STREAM_FLAG_CLOSED)) { + if(stream == NULL || (stream->flags & NGHTTP2_STREAM_FLAG_CLOSED) || + stream->state == NGHTTP2_STREAM_IDLE) { return NULL; } @@ -689,9 +690,12 @@ int nghttp2_session_add_item(nghttp2_session *session, case NGHTTP2_RST_STREAM: if(stream) { /* We rely on the stream state to decide whether number of - streams should be decremented or not. For purly reserved - streams, they are not counted to those numbers and we must - keep this state in order not to decrement the number. */ + streams should be decremented or not. For purly reserved or + idle streams, they are not counted to those numbers and we + must keep this state in order not to decrement the number. + We don't check against NGHTTP2_STREAM_IDLE because + nghttp2_session_get_stream() does not return such + stream. */ if(stream->state != NGHTTP2_STREAM_RESERVED) { stream->state = NGHTTP2_STREAM_CLOSING; } @@ -800,16 +804,41 @@ nghttp2_stream* nghttp2_session_open_stream(nghttp2_session *session, nghttp2_stream *stream; nghttp2_stream *dep_stream = NULL; nghttp2_stream *root_stream; + int stream_alloc = 0; nghttp2_priority_spec pri_spec_default; nghttp2_priority_spec *pri_spec = pri_spec_in; - if(session->server && !nghttp2_session_is_my_stream_id(session, stream_id)) { - nghttp2_session_adjust_closed_stream(session, 1); + stream = nghttp2_session_get_stream_raw(session, stream_id); + + if(stream) { + assert(stream->state == NGHTTP2_STREAM_IDLE); + assert(nghttp2_stream_in_dep_tree(stream)); + nghttp2_session_detach_closed_stream(session, stream); + nghttp2_stream_dep_remove(stream); + } else { + if(session->server && + (!nghttp2_session_is_my_stream_id(session, stream_id) || + initial_state == NGHTTP2_STREAM_IDLE)) { + nghttp2_session_adjust_closed_stream(session, 1); + } + + stream = malloc(sizeof(nghttp2_stream)); + if(stream == NULL) { + return NULL; + } + + stream_alloc = 1; } - stream = malloc(sizeof(nghttp2_stream)); - if(stream == NULL) { - return NULL; + if(pri_spec->stream_id != 0) { + dep_stream = nghttp2_session_get_stream_raw(session, pri_spec->stream_id); + + /* If dep_stream is not part of dependency tree, stream will get + default priority. */ + if(!dep_stream || !nghttp2_stream_in_dep_tree(dep_stream)) { + nghttp2_priority_spec_default_init(&pri_spec_default); + pri_spec = &pri_spec_default; + } } if(pri_spec->stream_id != 0) { @@ -829,13 +858,16 @@ nghttp2_stream* nghttp2_session_open_stream(nghttp2_session *session, session->local_settings.initial_window_size, stream_user_data); - rv = nghttp2_map_insert(&session->streams, &stream->map_entry); - if(rv != 0) { - free(stream); - return NULL; + if(stream_alloc) { + rv = nghttp2_map_insert(&session->streams, &stream->map_entry); + if(rv != 0) { + free(stream); + return NULL; + } } - if(initial_state == NGHTTP2_STREAM_RESERVED) { + switch(initial_state) { + case NGHTTP2_STREAM_RESERVED: if(nghttp2_session_is_my_stream_id(session, stream_id)) { /* half closed (remote) */ nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_RD); @@ -845,7 +877,14 @@ nghttp2_stream* nghttp2_session_open_stream(nghttp2_session *session, } /* Reserved stream does not count in the concurrent streams limit. That is one of the DOS vector. */ - } else { + break; + case NGHTTP2_STREAM_IDLE: + /* Idle stream does not count toward the concurrent streams limit. + This is used as anchor node in dependency tree. */ + assert(session->server); + nghttp2_session_keep_closed_stream(session, stream); + break; + default: if(nghttp2_session_is_my_stream_id(session, stream_id)) { ++session->num_outgoing_streams; } else { @@ -950,7 +989,11 @@ int nghttp2_session_close_stream(nghttp2_session *session, int32_t stream_id, } } - if(stream->state != NGHTTP2_STREAM_RESERVED) { + switch(stream->state) { + case NGHTTP2_STREAM_RESERVED: + case NGHTTP2_STREAM_IDLE: + break; + default: if(nghttp2_session_is_my_stream_id(session, stream_id)) { --session->num_outgoing_streams; } else { @@ -961,12 +1004,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->server && - nghttp2_stream_in_dep_tree(stream) && - !nghttp2_session_is_my_stream_id(session, stream_id)) { - /* On server side, retain incoming stream object at most - MAX_CONCURRENT_STREAMS combined with the current active streams - to make dependency tree work better. */ + if(session->server && nghttp2_stream_in_dep_tree(stream)) { + /* On server side, retain stream at most MAX_CONCURRENT_STREAMS + combined with the current active incoming streams to make + dependency tree work better. */ nghttp2_session_keep_closed_stream(session, stream); } else { nghttp2_session_destroy_stream(session, stream); @@ -991,11 +1032,12 @@ void nghttp2_session_destroy_stream(nghttp2_session *session, void nghttp2_session_keep_closed_stream(nghttp2_session *session, nghttp2_stream *stream) { - DEBUGF(fprintf(stderr, "stream: keep closed stream(%p)=%d\n", - stream, stream->stream_id)); + DEBUGF(fprintf(stderr, "stream: keep closed stream(%p)=%d, state=%d\n", + stream, stream->stream_id, stream->state)); if(session->closed_stream_tail) { session->closed_stream_tail->closed_next = stream; + stream->closed_prev = session->closed_stream_tail; } else { session->closed_stream_head = stream; } @@ -1006,6 +1048,48 @@ void nghttp2_session_keep_closed_stream(nghttp2_session *session, nghttp2_session_adjust_closed_stream(session, 0); } +void nghttp2_session_detach_closed_stream(nghttp2_session *session, + nghttp2_stream *stream) +{ + nghttp2_stream *prev_stream, *next_stream; + + DEBUGF(fprintf(stderr, "stream: detach closed stream(%p)=%d, state=%d\n", + stream, stream->stream_id, stream->state)); + + prev_stream = stream->closed_prev; + next_stream = stream->closed_next; + + if(prev_stream) { + prev_stream->closed_next = next_stream; + } else { + session->closed_stream_head = next_stream; + } + + if(next_stream) { + next_stream->closed_prev = prev_stream; + } else { + session->closed_stream_tail = prev_stream; + } + + stream->closed_prev = NULL; + stream->closed_next = NULL; + + --session->num_closed_streams; +} + +/* Returns nonzero if closed stream can not be added to linked list + now. */ +static int session_closed_stream_full(nghttp2_session *session) +{ + size_t num_stream_max; + + num_stream_max = nghttp2_min(session->local_settings.max_concurrent_streams, + session->pending_local_max_concurrent_stream); + + return (size_t)nghttp2_max(0, (ssize_t)session->num_closed_streams - 1) + + session->num_incoming_streams >= num_stream_max; +} + void nghttp2_session_adjust_closed_stream(nghttp2_session *session, ssize_t offset) { @@ -1029,7 +1113,9 @@ void nghttp2_session_adjust_closed_stream(nghttp2_session *session, session->closed_stream_head = head_stream->closed_next; - if(session->closed_stream_tail == head_stream) { + if(session->closed_stream_head) { + session->closed_stream_head->closed_prev = NULL; + } else { session->closed_stream_tail = NULL; } @@ -1233,28 +1319,6 @@ static int session_predicate_headers_send(nghttp2_session *session, return session_predicate_stream_frame_send(session, stream_id); } -/* - * This function checks PRIORITY frame with stream ID |stream_id| can - * be sent at this time. - */ -static int session_predicate_priority_send -(nghttp2_session *session, int32_t stream_id) -{ - nghttp2_stream *stream; - stream = nghttp2_session_get_stream(session, stream_id); - - /* PRIORITY must not be sent in reserved(local) */ - if(stream) { - if(state_reserved_local(session, stream)) { - return NGHTTP2_ERR_INVALID_STREAM_STATE; - } - } else if(session_detect_idle_stream(session, stream_id)) { - return NGHTTP2_ERR_INVALID_STREAM_ID; - } - - return 0; -} - /* * This function checks PUSH_PROMISE frame |frame| with stream ID * |stream_id| can be sent at this time. @@ -1668,15 +1732,18 @@ static int session_prep_frame(nghttp2_session *session, return framerv; } case NGHTTP2_PRIORITY: { - rv = session_predicate_priority_send(session, frame->hd.stream_id); - if(rv != 0) { - return rv; - } + /* PRIORITY frame can be sent at any time and to any stream + ID. */ framerv = nghttp2_frame_pack_priority(&session->aob.framebufs, &frame->priority); if(framerv < 0) { return framerv; } + + /* Peer can send PRIORITY frame against idle stream to create + "anchor" in dependency tree. Only client can do this in + nghttp2. In nghttp2, only server retains non-active (closed + or idle) streams in memory, so we don't open stream here. */ break; } case NGHTTP2_RST_STREAM: @@ -2170,24 +2237,24 @@ static int session_after_frame_sent(nghttp2_session *session) } case NGHTTP2_PRIORITY: { nghttp2_stream *stream; + + if(session->server) { + break; + } + stream = nghttp2_session_get_stream_raw(session, frame->hd.stream_id); + if(!stream) { break; } - /* Only update priority of the stream, only if it is not pushed - stream and is initiated by local peer, or it is pushed stream - and is initiated by remote peer */ - if(((stream->flags & NGHTTP2_STREAM_FLAG_PUSH) == 0 && - nghttp2_session_is_my_stream_id(session, frame->hd.stream_id)) || - ((stream->flags & NGHTTP2_STREAM_FLAG_PUSH) && - !nghttp2_session_is_my_stream_id(session, frame->hd.stream_id))) { - rv = nghttp2_session_reprioritize_stream(session, stream, - &frame->priority.pri_spec); - if(nghttp2_is_fatal(rv)) { - return rv; - } + rv = nghttp2_session_reprioritize_stream(session, stream, + &frame->priority.pri_spec); + + if(nghttp2_is_fatal(rv)) { + return rv; } + break; } case NGHTTP2_RST_STREAM: @@ -3202,26 +3269,32 @@ int nghttp2_session_on_priority_received(nghttp2_session *session, return session_handle_invalid_connection (session, frame, NGHTTP2_PROTOCOL_ERROR, "PRIORITY: stream_id == 0"); } - stream = nghttp2_session_get_stream_raw(session, frame->hd.stream_id); - if(!stream) { - if(session_detect_idle_stream(session, frame->hd.stream_id)) { - return session_handle_invalid_connection - (session, frame, NGHTTP2_PROTOCOL_ERROR, "PRIORITY: stream in idle"); - } - return 0; - } - if(state_reserved_remote(session, stream)) { - return session_handle_invalid_connection - (session, frame, NGHTTP2_PROTOCOL_ERROR, "PRIORITY: stream in reserved"); - } - /* Only update priority of the stream, only if it is not pushed - stream and is initiated by remote peer, or it is pushed stream - and is initiated by local peer */ - if(((stream->flags & NGHTTP2_STREAM_FLAG_PUSH) == 0 && - !nghttp2_session_is_my_stream_id(session, frame->hd.stream_id)) || - ((stream->flags & NGHTTP2_STREAM_FLAG_PUSH) && - nghttp2_session_is_my_stream_id(session, frame->hd.stream_id))) { + if(!session->server) { + /* Re-prioritization works only in server */ + return session_call_on_frame_received(session, frame); + } + + stream = nghttp2_session_get_stream_raw(session, frame->hd.stream_id); + + if(!stream) { + /* PRIORITY against idle stream can create anchor node in + dependency tree. */ + if(!session_detect_idle_stream(session, frame->hd.stream_id) || + session_closed_stream_full(session)) { + return 0; + } + + stream = nghttp2_session_open_stream(session, frame->hd.stream_id, + NGHTTP2_STREAM_FLAG_NONE, + &frame->priority.pri_spec, + NGHTTP2_STREAM_IDLE, + NULL); + + if(stream == NULL) { + return NGHTTP2_ERR_NOMEM; + } + } else { rv = nghttp2_session_reprioritize_stream(session, stream, &frame->priority.pri_spec); @@ -3229,6 +3302,7 @@ int nghttp2_session_on_priority_received(nghttp2_session *session, return rv; } } + return session_call_on_frame_received(session, frame); } diff --git a/lib/nghttp2_session.h b/lib/nghttp2_session.h index ac248600..d7c8e570 100644 --- a/lib/nghttp2_session.h +++ b/lib/nghttp2_session.h @@ -425,6 +425,12 @@ void nghttp2_session_destroy_stream(nghttp2_session *session, void nghttp2_session_keep_closed_stream(nghttp2_session *session, nghttp2_stream *stream); +/* + * Detaches |stream| from closed streams linked list. + */ +void nghttp2_session_detach_closed_stream(nghttp2_session *session, + nghttp2_stream *stream); + /* * Deletes closed stream to ensure that number of incoming streams * including active and closed is in the maximum number of allowed @@ -644,7 +650,8 @@ nghttp2_stream* nghttp2_session_get_stream(nghttp2_session *session, /* * This function behaves like nghttp2_session_get_stream(), but it - * returns stream object even if it is marked as closed. + * returns stream object even if it is marked as closed or in + * NGHTTP2_STREAM_IDLE state. */ nghttp2_stream* nghttp2_session_get_stream_raw(nghttp2_session *session, int32_t stream_id); diff --git a/lib/nghttp2_stream.c b/lib/nghttp2_stream.c index 3f0f4cfc..6dc385ce 100644 --- a/lib/nghttp2_stream.c +++ b/lib/nghttp2_stream.c @@ -57,6 +57,7 @@ void nghttp2_stream_init(nghttp2_stream *stream, int32_t stream_id, stream->sib_prev = NULL; stream->sib_next = NULL; + stream->closed_prev = NULL; stream->closed_next = NULL; stream->dpri = NGHTTP2_STREAM_DPRI_NO_DATA; diff --git a/lib/nghttp2_stream.h b/lib/nghttp2_stream.h index 77417399..2a670973 100644 --- a/lib/nghttp2_stream.h +++ b/lib/nghttp2_stream.h @@ -65,7 +65,10 @@ typedef enum { memory. */ NGHTTP2_STREAM_CLOSING, /* PUSH_PROMISE is received or sent */ - NGHTTP2_STREAM_RESERVED + NGHTTP2_STREAM_RESERVED, + /* Stream is created in this state if it is used as anchor in + dependency tree. */ + NGHTTP2_STREAM_IDLE } nghttp2_stream_state; typedef enum { @@ -126,11 +129,11 @@ struct nghttp2_stream { doubly-linked list and first element is pointed by roots->head. */ nghttp2_stream *root_prev, *root_next; - /* When stream is kept after closure, it may be kept in single + /* When stream is kept after closure, it may be kept in doubly linked list pointed by nghttp2_session closed_stream_head. closed_next points to the next stream object if it is the element of the list. */ - nghttp2_stream *closed_next; + nghttp2_stream *closed_prev, *closed_next; /* pointer to roots, which tracks dependency tree roots */ nghttp2_stream_roots *roots; /* The arbitrary data provided by user for this stream. */ diff --git a/tests/main.c b/tests/main.c index b5a56a4f..5b0c49fb 100644 --- a/tests/main.c +++ b/tests/main.c @@ -243,6 +243,8 @@ int main(int argc, char* argv[]) test_nghttp2_session_stream_attach_data_subtree) || !CU_add_test(pSuite, "session_stream_keep_closed_stream", test_nghttp2_session_keep_closed_stream) || + !CU_add_test(pSuite, "session_detach_closed_stream", + test_nghttp2_session_detach_closed_stream) || !CU_add_test(pSuite, "session_large_dep_tree", test_nghttp2_session_large_dep_tree) || !CU_add_test(pSuite, "session_graceful_shutdown", @@ -253,6 +255,8 @@ int main(int argc, char* argv[]) test_nghttp2_session_recv_client_preface) || !CU_add_test(pSuite, "session_delete_data_item", test_nghttp2_session_delete_data_item) || + !CU_add_test(pSuite, "session_open_idle_stream", + test_nghttp2_session_open_idle_stream) || !CU_add_test(pSuite, "frame_pack_headers", test_nghttp2_frame_pack_headers) || !CU_add_test(pSuite, "frame_pack_headers_frame_too_large", diff --git a/tests/nghttp2_session_test.c b/tests/nghttp2_session_test.c index 49351db3..acdec7fa 100644 --- a/tests/nghttp2_session_test.c +++ b/tests/nghttp2_session_test.c @@ -2030,16 +2030,7 @@ void test_nghttp2_session_on_priority_received(void) nghttp2_frame_priority_init(&frame.priority, 1, &pri_spec); - /* non-push and initiated by remote peer */ - CU_ASSERT(0 == nghttp2_session_on_priority_received(session, &frame)); - - CU_ASSERT(2 == stream->weight); - - /* push and initiated by remote peer: no update */ - stream->flags = NGHTTP2_STREAM_FLAG_PUSH; - - frame.priority.pri_spec.weight = 3; - + /* depend on stream 0 */ CU_ASSERT(0 == nghttp2_session_on_priority_received(session, &frame)); CU_ASSERT(2 == stream->weight); @@ -2054,35 +2045,23 @@ void test_nghttp2_session_on_priority_received(void) NGHTTP2_STREAM_OPENING, NULL); frame.hd.stream_id = 2; - /* non-push and initiated by local peer: no update */ - CU_ASSERT(0 == nghttp2_session_on_priority_received(session, &frame)); - - CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == stream->weight); - - /* push and initiated by local peer */ - stream->flags = NGHTTP2_STREAM_FLAG_PUSH; + /* using dependency stream */ nghttp2_priority_spec_init(&frame.priority.pri_spec, 3, 1, 0); CU_ASSERT(0 == nghttp2_session_on_priority_received(session, &frame)); CU_ASSERT(dep_stream == stream->dep_prev); - nghttp2_frame_priority_free(&frame.priority); - nghttp2_session_del(session); + /* PRIORITY against idle stream */ - /* Check that receiving PRIORITY in reserved(remote) is error */ - nghttp2_session_server_new(&session, &callbacks, &user_data); - nghttp2_session_open_stream(session, 3, NGHTTP2_STREAM_FLAG_NONE, - &pri_spec_default, - NGHTTP2_STREAM_RESERVED, NULL); + frame.hd.stream_id = 100; - nghttp2_frame_priority_init(&frame.priority, 3, &pri_spec_default); - - user_data.frame_recv_cb_called = 0; - user_data.invalid_frame_recv_cb_called = 0; CU_ASSERT(0 == nghttp2_session_on_priority_received(session, &frame)); - CU_ASSERT(0 == user_data.frame_recv_cb_called); - CU_ASSERT(1 == user_data.invalid_frame_recv_cb_called); + + stream = nghttp2_session_get_stream_raw(session, frame.hd.stream_id); + + CU_ASSERT(NGHTTP2_STREAM_IDLE == stream->state); + CU_ASSERT(dep_stream == stream->dep_prev); nghttp2_frame_priority_free(&frame.priority); nghttp2_session_del(session); @@ -3743,7 +3722,6 @@ void test_nghttp2_submit_priority(void) memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); callbacks.send_callback = null_send_callback; callbacks.on_frame_send_callback = on_frame_send_callback; - callbacks.on_frame_not_send_callback = on_frame_not_send_callback; nghttp2_session_client_new(&session, &callbacks, &ud); stream = nghttp2_session_open_stream(session, 1, NGHTTP2_STREAM_FLAG_NONE, @@ -3752,56 +3730,19 @@ void test_nghttp2_submit_priority(void) nghttp2_priority_spec_init(&pri_spec, 0, 3, 0); - /* non-push stream and initiated by local peer */ + /* depends on stream 0 */ CU_ASSERT(0 == nghttp2_submit_priority(session, NGHTTP2_FLAG_NONE, 1, &pri_spec)); CU_ASSERT(0 == nghttp2_session_send(session)); CU_ASSERT(3 == stream->weight); - /* push stream and initiated by local peer: no update */ - stream->flags = NGHTTP2_STREAM_FLAG_PUSH; - - nghttp2_priority_spec_init(&pri_spec, 0, 2, 0); - - CU_ASSERT(0 == nghttp2_submit_priority(session, NGHTTP2_FLAG_NONE, 1, - &pri_spec)); - CU_ASSERT(0 == nghttp2_session_send(session)); - - CU_ASSERT(3 == stream->weight); - - /* non-push stream and initiated by remote peer: no update */ - stream = nghttp2_session_open_stream(session, 2, NGHTTP2_STREAM_FLAG_NONE, - &pri_spec_default, - NGHTTP2_STREAM_OPENING, NULL); - CU_ASSERT(0 == nghttp2_submit_priority(session, NGHTTP2_FLAG_NONE, 2, - &pri_spec)); - CU_ASSERT(0 == nghttp2_session_send(session)); - CU_ASSERT(NGHTTP2_DEFAULT_WEIGHT == stream->weight); - - /* push stream and initiated by remote peer */ - stream->flags = NGHTTP2_STREAM_FLAG_PUSH; - CU_ASSERT(0 == nghttp2_submit_priority(session, NGHTTP2_FLAG_NONE, 2, - &pri_spec)); - CU_ASSERT(0 == nghttp2_session_send(session)); - CU_ASSERT(2 == stream->weight); - - nghttp2_session_del(session); - - /* Check that transmission of PRIORITY in reserved(local) is - error */ - nghttp2_session_server_new(&session, &callbacks, &ud); - stream = nghttp2_session_open_stream(session, 2, NGHTTP2_STREAM_FLAG_NONE, - &pri_spec_default, - NGHTTP2_STREAM_RESERVED, NULL); - - CU_ASSERT(0 == nghttp2_submit_priority(session, NGHTTP2_FLAG_NONE, 2, + /* submit against idle stream */ + CU_ASSERT(0 == nghttp2_submit_priority(session, NGHTTP2_FLAG_NONE, 3, &pri_spec)); ud.frame_send_cb_called = 0; - ud.frame_not_send_cb_called = 0; CU_ASSERT(0 == nghttp2_session_send(session)); - CU_ASSERT(0 == ud.frame_send_cb_called); - CU_ASSERT(1 == ud.frame_not_send_cb_called); + CU_ASSERT(1 == ud.frame_send_cb_called); nghttp2_session_del(session); } @@ -6359,7 +6300,6 @@ void test_nghttp2_session_keep_closed_stream(void) { nghttp2_session *session; nghttp2_session_callbacks callbacks; - /* nghttp2_stream *stream; */ const size_t max_concurrent_streams = 5; nghttp2_settings_entry iv = { NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS, @@ -6391,12 +6331,20 @@ void test_nghttp2_session_keep_closed_stream(void) CU_ASSERT(2 == session->num_closed_streams); CU_ASSERT(5 == session->closed_stream_tail->stream_id); CU_ASSERT(1 == session->closed_stream_head->stream_id); + CU_ASSERT(session->closed_stream_head == + session->closed_stream_tail->closed_prev); + CU_ASSERT(NULL == session->closed_stream_tail->closed_next); + CU_ASSERT(session->closed_stream_tail == + session->closed_stream_head->closed_next); + CU_ASSERT(NULL == session->closed_stream_head->closed_prev); open_stream(session, 11); CU_ASSERT(1 == session->num_closed_streams); CU_ASSERT(5 == session->closed_stream_tail->stream_id); CU_ASSERT(session->closed_stream_tail == session->closed_stream_head); + CU_ASSERT(NULL == session->closed_stream_head->closed_prev); + CU_ASSERT(NULL == session->closed_stream_head->closed_next); open_stream(session, 13); @@ -6407,6 +6355,88 @@ void test_nghttp2_session_keep_closed_stream(void) nghttp2_session_del(session); } +void test_nghttp2_session_detach_closed_stream(void) +{ + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + int i; + nghttp2_stream *stream; + + memset(&callbacks, 0, sizeof(callbacks)); + callbacks.send_callback = null_send_callback; + + nghttp2_session_server_new(&session, &callbacks, NULL); + + for(i = 0; i < 3; ++i) { + open_stream(session, i); + nghttp2_session_close_stream(session, i, NGHTTP2_NO_ERROR); + } + + CU_ASSERT(3 == session->num_closed_streams); + + stream = nghttp2_session_get_stream_raw(session, 1); + + CU_ASSERT(session->closed_stream_head == stream->closed_prev); + CU_ASSERT(session->closed_stream_tail == stream->closed_next); + CU_ASSERT(stream == session->closed_stream_head->closed_next); + CU_ASSERT(stream == session->closed_stream_tail->closed_prev); + + nghttp2_session_detach_closed_stream(session, stream); + + CU_ASSERT(2 == session->num_closed_streams); + + CU_ASSERT(NULL == stream->closed_prev); + CU_ASSERT(NULL == stream->closed_next); + + CU_ASSERT(session->closed_stream_head == + session->closed_stream_tail->closed_prev); + CU_ASSERT(session->closed_stream_tail == + session->closed_stream_head->closed_next); + + /* Close head stream */ + stream = session->closed_stream_head; + + nghttp2_session_detach_closed_stream(session, stream); + + CU_ASSERT(1 == session->num_closed_streams); + + CU_ASSERT(session->closed_stream_head == session->closed_stream_tail); + CU_ASSERT(NULL == session->closed_stream_head->closed_prev); + CU_ASSERT(NULL == session->closed_stream_head->closed_next); + + /* Close last stream */ + + stream = session->closed_stream_head; + + nghttp2_session_detach_closed_stream(session, stream); + + CU_ASSERT(0 == session->num_closed_streams); + + CU_ASSERT(NULL == session->closed_stream_head); + CU_ASSERT(NULL == session->closed_stream_tail); + + for(i = 3; i < 5; ++i) { + open_stream(session, i); + nghttp2_session_close_stream(session, i, NGHTTP2_NO_ERROR); + } + + CU_ASSERT(2 == session->num_closed_streams); + + /* Close tail stream */ + + stream = session->closed_stream_tail; + + nghttp2_session_detach_closed_stream(session, stream); + + CU_ASSERT(1 == session->num_closed_streams); + + CU_ASSERT(session->closed_stream_head == session->closed_stream_tail); + CU_ASSERT(NULL == session->closed_stream_head->closed_prev); + CU_ASSERT(NULL == session->closed_stream_head->closed_next); + + nghttp2_session_del(session); +} + void test_nghttp2_session_large_dep_tree(void) { nghttp2_session *session; @@ -6635,3 +6665,47 @@ void test_nghttp2_session_delete_data_item(void) nghttp2_session_del(session); } + +void test_nghttp2_session_open_idle_stream(void) +{ + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + nghttp2_stream *stream; + nghttp2_stream *opened_stream; + nghttp2_priority_spec pri_spec; + nghttp2_frame frame; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + + nghttp2_session_server_new(&session, &callbacks, NULL); + + nghttp2_priority_spec_init(&pri_spec, 0, 3, 0); + + nghttp2_frame_priority_init(&frame.priority, 1, &pri_spec); + + CU_ASSERT(0 == nghttp2_session_on_priority_received(session, &frame)); + + stream = nghttp2_session_get_stream_raw(session, 1); + + CU_ASSERT(NGHTTP2_STREAM_IDLE == stream->state); + CU_ASSERT(NULL == stream->closed_prev); + CU_ASSERT(NULL == stream->closed_next); + CU_ASSERT(1 == session->num_closed_streams); + CU_ASSERT(session->closed_stream_head == stream); + CU_ASSERT(session->closed_stream_tail == stream); + + opened_stream = nghttp2_session_open_stream(session, 1, + NGHTTP2_STREAM_FLAG_NONE, + &pri_spec_default, + NGHTTP2_STREAM_OPENING, NULL); + + CU_ASSERT(stream == opened_stream); + CU_ASSERT(NGHTTP2_STREAM_OPENING == stream->state); + CU_ASSERT(0 == session->num_closed_streams); + CU_ASSERT(NULL == session->closed_stream_head); + CU_ASSERT(NULL == session->closed_stream_tail); + + nghttp2_frame_priority_free(&frame.priority); + + nghttp2_session_del(session); +} diff --git a/tests/nghttp2_session_test.h b/tests/nghttp2_session_test.h index 8cb69f1f..fe64849c 100644 --- a/tests/nghttp2_session_test.h +++ b/tests/nghttp2_session_test.h @@ -111,10 +111,12 @@ void test_nghttp2_session_stream_dep_all_your_stream_are_belong_to_us(void); void test_nghttp2_session_stream_attach_data(void); void test_nghttp2_session_stream_attach_data_subtree(void); void test_nghttp2_session_keep_closed_stream(void); +void test_nghttp2_session_detach_closed_stream(void); void test_nghttp2_session_large_dep_tree(void); void test_nghttp2_session_graceful_shutdown(void); void test_nghttp2_session_on_header_temporal_failure(void); void test_nghttp2_session_recv_client_preface(void); void test_nghttp2_session_delete_data_item(void); +void test_nghttp2_session_open_idle_stream(void); #endif /* NGHTTP2_SESSION_TEST_H */