diff --git a/lib/includes/spdylay/spdylay.h b/lib/includes/spdylay/spdylay.h index 486e3bc6..acc71d1b 100644 --- a/lib/includes/spdylay/spdylay.h +++ b/lib/includes/spdylay/spdylay.h @@ -52,6 +52,15 @@ typedef enum { SPDYLAY_ERR_INVALID_FRAME = -506, SPDYLAY_ERR_EOF = -507, SPDYLAY_ERR_DEFERRED = -508, + SPDYLAY_ERR_STREAM_ID_NOT_AVAILABLE = -509, + SPDYLAY_ERR_STREAM_ALREADY_CLOSED = -510, + SPDYLAY_ERR_STREAM_CLOSING = -511, + SPDYLAY_ERR_STREAM_SHUT_WR = -512, + SPDYLAY_ERR_INVALID_STREAM_ID = -513, + SPDYLAY_ERR_INVALID_STREAM_STATE = -514, + SPDYLAY_ERR_DEFERRED_DATA_EXIST = -515, + SPDYLAY_ERR_SYN_STREAM_NOT_ALLOWED = -516, + SPDYLAY_ERR_GOAWAY_ALREADY_SENT = -517, /* The errors < SPDYLAY_ERR_FATAL mean that the library is under unexpected condition that it cannot process any further data reliably (e.g., out of memory). */ @@ -337,6 +346,15 @@ typedef void (*spdylay_on_ctrl_send_callback) (spdylay_session *session, spdylay_frame_type type, spdylay_frame *frame, void *user_data); +/* + * Callback function invoked after the control frame |frame| of type + * |type| is not sent because of the error. The error is indicated by + * the |error|, which is one of the values defined in spdylay_error. + */ +typedef void (*spdylay_on_ctrl_not_send_callback) +(spdylay_session *session, spdylay_frame_type type, spdylay_frame *frame, + int error, void *user_data); + /* * Callback function invoked after DATA frame is sent. */ @@ -381,6 +399,7 @@ typedef struct { spdylay_on_data_recv_callback on_data_recv_callback; spdylay_before_ctrl_send_callback before_ctrl_send_callback; spdylay_on_ctrl_send_callback on_ctrl_send_callback; + spdylay_on_ctrl_not_send_callback on_ctrl_not_send_callback; spdylay_on_data_send_callback on_data_send_callback; spdylay_on_stream_close_callback on_stream_close_callback; spdylay_on_request_recv_callback on_request_recv_callback; diff --git a/lib/spdylay_session.c b/lib/spdylay_session.c index 17736e0b..abe0262d 100644 --- a/lib/spdylay_session.c +++ b/lib/spdylay_session.c @@ -43,6 +43,14 @@ static int spdylay_session_get_max_concurrent_streams_reached <= spdylay_map_size(&session->streams); } +/* + * Returns non-zero if |error| is non-fatal error. + */ +static int spdylay_is_non_fatal(int error) +{ + return error < 0 && error > SPDYLAY_ERR_FATAL; +} + int spdylay_session_is_my_stream_id(spdylay_session *session, int32_t stream_id) { @@ -438,54 +446,129 @@ void spdylay_session_close_pushed_streams(spdylay_session *session, } } -/* - * Returns non-zero value if local peer can send SYN_REPLY with stream - * ID |stream_id| at the moment, or 0. - */ -static int spdylay_session_is_reply_allowed(spdylay_session *session, - int32_t stream_id) +static int spdylay_predicate_stream_for_send(spdylay_stream *stream) { - spdylay_stream *stream = spdylay_session_get_stream(session, stream_id); - if(stream == NULL || (stream->shut_flags & SPDYLAY_SHUT_WR)) { - return 0; - } - if(spdylay_session_is_my_stream_id(session, stream_id)) { - return 0; + if(stream == NULL) { + return SPDYLAY_ERR_STREAM_ALREADY_CLOSED; + } else if(stream->shut_flags & SPDYLAY_SHUT_WR) { + return SPDYLAY_ERR_STREAM_SHUT_WR; } else { - return stream->state == SPDYLAY_STREAM_OPENING; + return 0; } } /* - * Returns nonzero value if local endpoint can send HEADERS with - * stream ID |stream_id| at the moment. + * This function checks SYN_REPLY with the stream ID |stream_id| can + * be sent at this time. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * SPDYLAY_ERR_STREAM_ALREADY_CLOSED + * The stream is already closed or does not exist. + * SPDYLAY_ERR_STREAM_SHUT_WR + * The transmission is not allowed for this stream (e.g., a frame + * with FIN flag set has already sent) + * SPDYLAY_ERR_INVALID_STREAM_ID + * The stream ID is invalid. + * SPDYLAY_ERR_STREAM_CLOSING + * RST_STREAM was queued for this stream. + * SPDYLAY_ERR_INVALID_STREAM_STATE + * The state of the stream is not valid (e.g., SYN_REPLY has + * already sent). */ -static int spdylay_session_is_headers_allowed(spdylay_session *session, - int32_t stream_id) -{ - spdylay_stream *stream = spdylay_session_get_stream(session, stream_id); - if(stream == NULL || (stream->shut_flags & SPDYLAY_SHUT_WR)) { - return 0; - } - if(spdylay_session_is_my_stream_id(session, stream_id)) { - return stream->state != SPDYLAY_STREAM_CLOSING; - } else { - return stream->state == SPDYLAY_STREAM_OPENED; - } -} - -/* - * Returns nonzero value if local endpoint can send WINDOW_UPDATE with - * stream ID |stream_id| at the moment. - */ -static int spdylay_session_is_window_update_allowed(spdylay_session *session, +static int spdylay_session_predicate_syn_reply_send(spdylay_session *session, int32_t stream_id) { spdylay_stream *stream = spdylay_session_get_stream(session, stream_id); - if(stream == NULL) { - return 0; + int r; + r = spdylay_predicate_stream_for_send(stream); + if(r != 0) { + return r; + } + if(spdylay_session_is_my_stream_id(session, stream_id)) { + return SPDYLAY_ERR_INVALID_STREAM_ID; + } else { + if(stream->state == SPDYLAY_STREAM_OPENING) { + return 0; + } else if(stream->state == SPDYLAY_STREAM_CLOSING) { + return SPDYLAY_ERR_STREAM_CLOSING; + } else { + return SPDYLAY_ERR_INVALID_STREAM_STATE; + } + } +} + +/* + * This function checks HEADERS with the stream ID |stream_id| can + * be sent at this time. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * SPDYLAY_ERR_STREAM_ALREADY_CLOSED + * The stream is already closed or does not exist. + * SPDYLAY_ERR_STREAM_SHUT_WR + * The transmission is not allowed for this stream (e.g., a frame + * with FIN flag set has already sent) + * SPDYLAY_ERR_STREAM_CLOSING + * RST_STREAM was queued for this stream. + * SPDYLAY_ERR_INVALID_STREAM_STATE + * The state of the stream is not valid (e.g., if the local peer + * is receiving side and SYN_REPLY has not been sent). + */ +static int spdylay_session_predicate_headers_send(spdylay_session *session, + int32_t stream_id) +{ + spdylay_stream *stream = spdylay_session_get_stream(session, stream_id); + int r; + r = spdylay_predicate_stream_for_send(stream); + if(r != 0) { + return r; + } + if(spdylay_session_is_my_stream_id(session, stream_id)) { + if(stream->state != SPDYLAY_STREAM_CLOSING) { + return 0; + } else { + return SPDYLAY_ERR_STREAM_CLOSING; + } + } else { + if(stream->state == SPDYLAY_STREAM_OPENED) { + return 0; + } else if(stream->state == SPDYLAY_STREAM_CLOSING) { + return SPDYLAY_ERR_STREAM_CLOSING; + } else { + return SPDYLAY_ERR_INVALID_STREAM_STATE; + } + } +} + +/* + * 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 + * does not affect the transmission of the WINDOW_UPDATE frame. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * SPDYLAY_ERR_STREAM_ALREADY_CLOSED + * The stream is already closed or does not exist. + * SPDYLAY_ERR_STREAM_CLOSING + * RST_STREAM was queued for this stream. + */ +static int spdylay_session_predicate_window_update_send +(spdylay_session *session, + int32_t stream_id) +{ + spdylay_stream *stream = spdylay_session_get_stream(session, stream_id); + if(stream == NULL) { + return SPDYLAY_ERR_STREAM_ALREADY_CLOSED; + } + if(stream->state != SPDYLAY_STREAM_CLOSING) { + return 0; + } else { + return SPDYLAY_ERR_STREAM_CLOSING; } - return stream->state != SPDYLAY_STREAM_CLOSING; } /* @@ -506,18 +589,40 @@ static size_t spdylay_session_next_data_read(spdylay_session *session, } } -static int spdylay_session_is_data_allowed(spdylay_session *session, - int32_t stream_id) +/* + * This function checks DATA with the stream ID |stream_id| can be + * sent at this time. + * + * This function returns 0 if it succeeds, or one of the following + * negative error codes: + * + * SPDYLAY_ERR_STREAM_ALREADY_CLOSED + * The stream is already closed or does not exist. + * SPDYLAY_ERR_STREAM_SHUT_WR + * The transmission is not allowed for this stream (e.g., a frame + * with FIN flag set has already sent) + * SPDYLAY_ERR_DEFERRED_DATA_EXIST + * Another DATA frame has already been deferred. + * SPDYLAY_ERR_STREAM_CLOSING + * RST_STREAM was queued for this stream. + * SPDYLAY_ERR_INVALID_STREAM_STATE + * The state of the stream is not valid (e.g., if the local peer + * is receiving side and SYN_REPLY has not been sent). + */ +static int spdylay_session_predicate_data_send(spdylay_session *session, + int32_t stream_id) { spdylay_stream *stream = spdylay_session_get_stream(session, stream_id); - if(stream == NULL || (stream->shut_flags & SPDYLAY_SHUT_WR)) { - return 0; + int r; + r = spdylay_predicate_stream_for_send(stream); + if(r != 0) { + return r; } if(stream->deferred_data != NULL) { /* stream->deferred_data != NULL means previously queued DATA frame has not been sent. We don't allow new DATA frame is sent in this case. */ - return 0; + return SPDYLAY_ERR_DEFERRED_DATA_EXIST; } if(spdylay_session_is_my_stream_id(session, stream_id)) { /* If stream->state is SPDYLAY_STREAM_CLOSING, RST_STREAM was @@ -528,9 +633,19 @@ static int spdylay_session_is_data_allowed(spdylay_session *session, frames are sent. This is not desirable situation; we want to close stream as soon as possible. To achieve this, we remove DATA frame before RST_STREAM. */ - return stream->state != SPDYLAY_STREAM_CLOSING; + if(stream->state != SPDYLAY_STREAM_CLOSING) { + return 0; + } else { + return SPDYLAY_ERR_STREAM_CLOSING; + } } else { - return stream->state == SPDYLAY_STREAM_OPENED; + if(stream->state == SPDYLAY_STREAM_OPENED) { + return 0; + } else if(stream->state == SPDYLAY_STREAM_CLOSING) { + return SPDYLAY_ERR_STREAM_CLOSING; + } else { + return SPDYLAY_ERR_INVALID_STREAM_STATE; + } } } @@ -542,14 +657,19 @@ static ssize_t spdylay_session_prep_frame(spdylay_session *session, ssize_t framebuflen; switch(item->frame_type) { case SPDYLAY_SYN_STREAM: { - uint32_t stream_id; + int32_t stream_id; spdylay_syn_stream_aux_data *aux_data; if(session->goaway_flags) { /* When GOAWAY is sent or received, peer must not send new SYN_STREAM. */ - return SPDYLAY_ERR_INVALID_FRAME; + return SPDYLAY_ERR_SYN_STREAM_NOT_ALLOWED; + } + /* All 32bit signed stream IDs are spent. */ + if(session->next_stream_id > INT32_MAX) { + return SPDYLAY_ERR_STREAM_ID_NOT_AVAILABLE; } stream_id = session->next_stream_id; + item->frame->syn_stream.stream_id = stream_id; session->next_stream_id += 2; if(session->version == SPDYLAY_PROTO_SPDY2) { @@ -578,9 +698,11 @@ static ssize_t spdylay_session_prep_frame(spdylay_session *session, break; } case SPDYLAY_SYN_REPLY: { - if(!spdylay_session_is_reply_allowed(session, - item->frame->syn_reply.stream_id)) { - return SPDYLAY_ERR_INVALID_FRAME; + int r; + r = spdylay_session_predicate_syn_reply_send + (session, item->frame->syn_reply.stream_id); + if(r != 0) { + return r; } if(session->version == SPDYLAY_PROTO_SPDY2) { spdylay_frame_nv_3to2(item->frame->syn_reply.nv); @@ -628,9 +750,11 @@ static ssize_t spdylay_session_prep_frame(spdylay_session *session, } break; case SPDYLAY_HEADERS: { - if(!spdylay_session_is_headers_allowed(session, - item->frame->headers.stream_id)) { - return SPDYLAY_ERR_INVALID_FRAME; + int r; + r = spdylay_session_predicate_headers_send(session, + item->frame->headers.stream_id); + if(r != 0) { + return r; } if(session->version == SPDYLAY_PROTO_SPDY2) { spdylay_frame_nv_3to2(item->frame->headers.nv); @@ -650,9 +774,11 @@ static ssize_t spdylay_session_prep_frame(spdylay_session *session, break; } case SPDYLAY_WINDOW_UPDATE: { - if(!spdylay_session_is_window_update_allowed - (session, item->frame->window_update.stream_id)) { - return SPDYLAY_ERR_INVALID_FRAME; + int r; + r = spdylay_session_predicate_window_update_send + (session, item->frame->window_update.stream_id); + if(r != 0) { + return r; } framebuflen = spdylay_frame_pack_window_update(&session->aob.framebuf, &session->aob.framebufmax, @@ -668,7 +794,7 @@ static ssize_t spdylay_session_prep_frame(spdylay_session *session, exchange GOAWAY. This implementation allows receiver of first GOAWAY can sent its own GOAWAY to tell the remote peer that last-good-stream-id. */ - return SPDYLAY_ERR_INVALID_FRAME; + return SPDYLAY_ERR_GOAWAY_ALREADY_SENT; } framebuflen = spdylay_frame_pack_goaway(&session->aob.framebuf, &session->aob.framebufmax, @@ -680,8 +806,11 @@ static ssize_t spdylay_session_prep_frame(spdylay_session *session, case SPDYLAY_DATA: { size_t next_readmax; spdylay_stream *stream; - if(!spdylay_session_is_data_allowed(session, item->frame->data.stream_id)) { - return SPDYLAY_ERR_INVALID_FRAME; + int r; + r = spdylay_session_predicate_data_send(session, + item->frame->data.stream_id); + if(r != 0) { + return r; } stream = spdylay_session_get_stream(session, item->frame->data.stream_id); /* Assuming stream is not NULL */ @@ -935,7 +1064,8 @@ static int spdylay_session_after_frame_sent(spdylay_session *session) /* If session is closed or RST_STREAM was queued, we won't send further data. */ if(frame->data.eof || - !spdylay_session_is_data_allowed(session, frame->data.stream_id)) { + spdylay_session_predicate_data_send(session, + frame->data.stream_id) != 0) { spdylay_active_outbound_item_reset(&session->aob); } else { spdylay_outbound_item* item = spdylay_session_get_next_ob_item(session); @@ -1012,7 +1142,18 @@ int spdylay_session_send(spdylay_session *session) if(framebuflen == SPDYLAY_ERR_DEFERRED) { continue; } else if(framebuflen < 0) { - /* TODO Call error callback? */ + /* The library is responsible for the transmission of + WINDOW_UPDATE frame, so we don't call error callback for + it. */ + if(session->callbacks.on_ctrl_not_send_callback && + spdylay_is_non_fatal(framebuflen) && + item->frame_type != SPDYLAY_WINDOW_UPDATE) { + session->callbacks.on_ctrl_not_send_callback(session, + item->frame_type, + item->frame, + framebuflen, + session->user_data); + } spdylay_outbound_item_free(item); free(item); if(framebuflen < SPDYLAY_ERR_FATAL) { @@ -1510,14 +1651,6 @@ static int spdylay_session_fail_session(spdylay_session *session, return spdylay_submit_goaway(session, status_code); } -/* - * Returns non-zero if |error| is non-fatal error. - */ -static int spdylay_is_non_fatal(int error) -{ - return error < 0 && error > SPDYLAY_ERR_FATAL; -} - /* For errors, this function only returns FATAL error. */ static int spdylay_session_process_ctrl_frame(spdylay_session *session) { diff --git a/lib/spdylay_session.h b/lib/spdylay_session.h index 30928c86..7dfe43f5 100644 --- a/lib/spdylay_session.h +++ b/lib/spdylay_session.h @@ -109,7 +109,8 @@ struct spdylay_session { SPDYLAY_PROTO_SPDY3 */ uint16_t version; uint8_t server; - int32_t next_stream_id; + /* Next Stream ID. Made unsigned int to detect >= (1 << 31). */ + uint32_t next_stream_id; int32_t last_recv_stream_id; /* Counter of unique ID of PING. Wraps when it exceeds SPDYLAY_MAX_UNIQUE_ID */ diff --git a/tests/main.c b/tests/main.c index e15654e9..e87ef178 100644 --- a/tests/main.c +++ b/tests/main.c @@ -136,6 +136,8 @@ int main(int argc, char* argv[]) test_spdylay_session_defer_data) || !CU_add_test(pSuite, "session_flow_control", test_spdylay_session_flow_control) || + !CU_add_test(pSuite, "session_on_ctrl_not_send", + test_spdylay_session_on_ctrl_not_send) || !CU_add_test(pSuite, "frame_unpack_nv_spdy2", test_spdylay_frame_unpack_nv_spdy2) || !CU_add_test(pSuite, "frame_unpack_nv_spdy3", diff --git a/tests/spdylay_session_test.c b/tests/spdylay_session_test.c index bf933377..de66c279 100644 --- a/tests/spdylay_session_test.c +++ b/tests/spdylay_session_test.c @@ -52,6 +52,9 @@ typedef struct { int ctrl_recv_cb_called, invalid_ctrl_recv_cb_called; int ctrl_send_cb_called; spdylay_frame_type sent_frame_type; + int ctrl_not_send_cb_called; + spdylay_frame_type not_sent_frame_type; + int not_sent_error; int stream_close_cb_called; size_t data_source_length; int32_t stream_id; @@ -137,6 +140,18 @@ static void on_ctrl_send_callback(spdylay_session *session, ud->sent_frame_type = type; } +static void on_ctrl_not_send_callback(spdylay_session *session, + spdylay_frame_type type, + spdylay_frame *frame, + int error, + void *user_data) +{ + my_user_data *ud = (my_user_data*)user_data; + ++ud->ctrl_not_send_cb_called; + ud->not_sent_frame_type = type; + ud->not_sent_error = error; +} + static ssize_t fixed_length_data_source_read_callback (spdylay_session *session, int32_t stream_id, uint8_t *buf, size_t len, int *eof, @@ -1691,3 +1706,116 @@ void test_spdylay_session_flow_control() spdylay_frame_window_update_free(&frame.window_update); spdylay_session_del(session); } + +void test_spdylay_session_on_ctrl_not_send() +{ + spdylay_session *session; + spdylay_session_callbacks callbacks; + my_user_data user_data; + spdylay_stream *stream; + const char *nv[] = { NULL }; + + memset(&callbacks, 0, sizeof(spdylay_session_callbacks)); + callbacks.on_ctrl_not_send_callback = on_ctrl_not_send_callback; + callbacks.send_callback = null_send_callback; + user_data.ctrl_not_send_cb_called = 0; + user_data.not_sent_frame_type = 0; + user_data.not_sent_error = 0; + + CU_ASSERT(spdylay_session_server_new(&session, SPDYLAY_PROTO_SPDY2, + &callbacks, &user_data) == 0); + stream = spdylay_session_open_stream(session, 1, + SPDYLAY_CTRL_FLAG_NONE, + 3, SPDYLAY_STREAM_OPENED, &user_data); + /* Check SYN_REPLY */ + CU_ASSERT(0 == + spdylay_submit_syn_reply(session, SPDYLAY_CTRL_FLAG_FIN, 1, nv)); + CU_ASSERT(0 == spdylay_session_send(session)); + CU_ASSERT(1 == user_data.ctrl_not_send_cb_called); + CU_ASSERT(SPDYLAY_SYN_REPLY == user_data.not_sent_frame_type); + CU_ASSERT(SPDYLAY_ERR_INVALID_STREAM_STATE == user_data.not_sent_error); + + stream->state = SPDYLAY_STREAM_OPENING; + user_data.ctrl_not_send_cb_called = 0; + /* Send bogus stream ID */ + CU_ASSERT(0 == + spdylay_submit_syn_reply(session, SPDYLAY_CTRL_FLAG_FIN, 3, nv)); + CU_ASSERT(0 == spdylay_session_send(session)); + CU_ASSERT(1 == user_data.ctrl_not_send_cb_called); + CU_ASSERT(SPDYLAY_SYN_REPLY == user_data.not_sent_frame_type); + CU_ASSERT(SPDYLAY_ERR_STREAM_ALREADY_CLOSED == user_data.not_sent_error); + + user_data.ctrl_not_send_cb_called = 0; + /* Shutdown transmission */ + stream->shut_flags |= SPDYLAY_SHUT_WR; + CU_ASSERT(0 == + spdylay_submit_syn_reply(session, SPDYLAY_CTRL_FLAG_FIN, 1, nv)); + CU_ASSERT(0 == spdylay_session_send(session)); + CU_ASSERT(1 == user_data.ctrl_not_send_cb_called); + CU_ASSERT(SPDYLAY_SYN_REPLY == user_data.not_sent_frame_type); + CU_ASSERT(SPDYLAY_ERR_STREAM_SHUT_WR == user_data.not_sent_error); + + stream->shut_flags = SPDYLAY_SHUT_NONE; + user_data.ctrl_not_send_cb_called = 0; + /* Queue RST_STREAM */ + CU_ASSERT(0 == + spdylay_submit_syn_reply(session, SPDYLAY_CTRL_FLAG_FIN, 1, nv)); + CU_ASSERT(0 == spdylay_submit_rst_stream(session, 1, SPDYLAY_INTERNAL_ERROR)); + CU_ASSERT(0 == spdylay_session_send(session)); + CU_ASSERT(1 == user_data.ctrl_not_send_cb_called); + CU_ASSERT(SPDYLAY_SYN_REPLY == user_data.not_sent_frame_type); + CU_ASSERT(SPDYLAY_ERR_STREAM_CLOSING == user_data.not_sent_error); + + stream = spdylay_session_open_stream(session, 3, + SPDYLAY_CTRL_FLAG_NONE, + 3, SPDYLAY_STREAM_OPENED, &user_data); + + /* Check HEADERS */ + user_data.ctrl_not_send_cb_called = 0; + stream->state = SPDYLAY_STREAM_OPENING; + CU_ASSERT(0 == + spdylay_submit_headers(session, SPDYLAY_CTRL_FLAG_FIN, 3, nv)); + CU_ASSERT(0 == spdylay_session_send(session)); + CU_ASSERT(1 == user_data.ctrl_not_send_cb_called); + CU_ASSERT(SPDYLAY_HEADERS == user_data.not_sent_frame_type); + CU_ASSERT(SPDYLAY_ERR_INVALID_STREAM_STATE == user_data.not_sent_error); + + stream->state = SPDYLAY_STREAM_OPENED; + user_data.ctrl_not_send_cb_called = 0; + /* Queue RST_STREAM */ + CU_ASSERT(0 == + spdylay_submit_headers(session, SPDYLAY_CTRL_FLAG_FIN, 3, nv)); + CU_ASSERT(0 == spdylay_submit_rst_stream(session, 3, SPDYLAY_INTERNAL_ERROR)); + CU_ASSERT(0 == spdylay_session_send(session)); + CU_ASSERT(1 == user_data.ctrl_not_send_cb_called); + CU_ASSERT(SPDYLAY_HEADERS == user_data.not_sent_frame_type); + CU_ASSERT(SPDYLAY_ERR_STREAM_CLOSING == user_data.not_sent_error); + + spdylay_session_del(session); + + /* Check SYN_STREAM */ + user_data.ctrl_not_send_cb_called = 0; + CU_ASSERT(spdylay_session_client_new(&session, SPDYLAY_PROTO_SPDY2, + &callbacks, &user_data) == 0); + /* Maximum Stream ID is reached */ + session->next_stream_id = (1u << 31)+1; + CU_ASSERT(0 == spdylay_submit_syn_stream(session, SPDYLAY_CTRL_FLAG_FIN, 0, + 3, nv, NULL)); + CU_ASSERT(0 == spdylay_session_send(session)); + CU_ASSERT(1 == user_data.ctrl_not_send_cb_called); + CU_ASSERT(SPDYLAY_SYN_STREAM == user_data.not_sent_frame_type); + CU_ASSERT(SPDYLAY_ERR_STREAM_ID_NOT_AVAILABLE == user_data.not_sent_error); + + session->next_stream_id = 1; + user_data.ctrl_not_send_cb_called = 0; + /* Send GOAWAY */ + CU_ASSERT(0 == spdylay_submit_goaway(session, SPDYLAY_GOAWAY_OK)); + CU_ASSERT(0 == spdylay_submit_syn_stream(session, SPDYLAY_CTRL_FLAG_FIN, 0, + 3, nv, NULL)); + CU_ASSERT(0 == spdylay_session_send(session)); + CU_ASSERT(1 == user_data.ctrl_not_send_cb_called); + CU_ASSERT(SPDYLAY_SYN_STREAM == user_data.not_sent_frame_type); + CU_ASSERT(SPDYLAY_ERR_SYN_STREAM_NOT_ALLOWED == user_data.not_sent_error); + + spdylay_session_del(session); +} diff --git a/tests/spdylay_session_test.h b/tests/spdylay_session_test.h index 8b2ba2b8..3dc4910f 100644 --- a/tests/spdylay_session_test.h +++ b/tests/spdylay_session_test.h @@ -60,5 +60,6 @@ void test_spdylay_session_stream_close_on_syn_stream(); void test_spdylay_session_recv_invalid_frame(); void test_spdylay_session_defer_data(); void test_spdylay_session_flow_control(); +void test_spdylay_session_on_ctrl_not_send(); #endif // SPDYLAY_SESSION_TEST_H