Added on_ctrl_not_send_callback.

This callback function is invoked after the control frame
is not sent because of the error. The error is indicated by
the error argument, which is one of the values defined in spdylay_error.
This commit is contained in:
Tatsuhiro Tsujikawa 2012-03-08 00:18:18 +09:00
parent 185d929d86
commit 11020146f5
6 changed files with 353 additions and 69 deletions

View File

@ -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;

View File

@ -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)
{

View File

@ -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 */

View File

@ -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",

View File

@ -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);
}

View File

@ -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