diff --git a/examples/SpdyServer.cc b/examples/SpdyServer.cc index be116bb..51f5bce 100644 --- a/examples/SpdyServer.cc +++ b/examples/SpdyServer.cc @@ -402,7 +402,7 @@ ssize_t file_read_callback ssize_t r; while((r = read(fd, buf, length)) == -1 && errno == EINTR); if(r == -1) { - return SPDYLAY_ERR_CALLBACK_FAILURE; + return SPDYLAY_ERR_TEMPORAL_CALLBACK_FAILURE; } else { if(r == 0) { *eof = 1; diff --git a/lib/includes/spdylay/spdylay.h b/lib/includes/spdylay/spdylay.h index 742dd44..d7efb90 100644 --- a/lib/includes/spdylay/spdylay.h +++ b/lib/includes/spdylay/spdylay.h @@ -164,6 +164,10 @@ typedef enum { * The gzip error. */ SPDYLAY_ERR_GZIP = -520, + /** + * The user callback function failed due to the temporal error. + */ + SPDYLAY_ERR_TEMPORAL_CALLBACK_FAILURE = -521, /** * The errors < :enum:`SPDYLAY_ERR_FATAL` mean that the library is * under unexpected condition and cannot process any further data @@ -737,8 +741,11 @@ typedef union { * reading any data in this invocation. The library removes DATA * frame from the outgoing queue temporarily. To move back deferred * DATA frame to outgoing queue, call `spdylay_session_resume_data()`. - * In case of error, return :enum:`SPDYLAY_ERR_CALLBACK_FAILURE`, - * which leads to session failure. + * In case of error, there are 2 choices. Returning + * :enum:`SPDYLAY_ERR_TEMPORAL_CALLBACK_FAILURE` will close the stream + * by issuing RST_STREAM with :enum:`SPDYLAY_INTERNAL_ERROR`. + * Returning :enum:`SPDYLAY_ERR_CALLBACK_FAILURE` will signal the + * entire session failure. */ typedef ssize_t (*spdylay_data_source_read_callback) (spdylay_session *session, int32_t stream_id, diff --git a/lib/spdylay_helper.c b/lib/spdylay_helper.c index a11bc73..baee062 100644 --- a/lib/spdylay_helper.c +++ b/lib/spdylay_helper.c @@ -115,6 +115,8 @@ const char* spdylay_strerror(int error_code) return "Invalid state"; case SPDYLAY_ERR_GZIP: return "Gzip error"; + case SPDYLAY_ERR_TEMPORAL_CALLBACK_FAILURE: + return "The user callback function failed due to the temporal error"; case SPDYLAY_ERR_NOMEM: return "Out of memory"; case SPDYLAY_ERR_CALLBACK_FAILURE: diff --git a/lib/spdylay_session.c b/lib/spdylay_session.c index 972f67d..c95e8d9 100644 --- a/lib/spdylay_session.c +++ b/lib/spdylay_session.c @@ -1123,6 +1123,14 @@ static ssize_t spdylay_session_prep_frame(spdylay_session *session, if(framebuflen == SPDYLAY_ERR_DEFERRED) { spdylay_stream_defer_data(stream, item, SPDYLAY_DEFERRED_NONE); return SPDYLAY_ERR_DEFERRED; + } else if(framebuflen == SPDYLAY_ERR_TEMPORAL_CALLBACK_FAILURE) { + r = spdylay_session_add_rst_stream(session, data_frame->stream_id, + SPDYLAY_INTERNAL_ERROR); + if(r == 0) { + return framebuflen; + } else { + return r; + } } else if(framebuflen < 0) { return framebuflen; } @@ -1395,6 +1403,16 @@ static int spdylay_session_after_frame_sent(spdylay_session *session) SPDYLAY_DEFERRED_NONE); session->aob.item = NULL; spdylay_active_outbound_item_reset(&session->aob); + } else if(r == SPDYLAY_ERR_TEMPORAL_CALLBACK_FAILURE) { + /* Stop DATA frame chain and issue RST_STREAM to close the + stream. We don't return + SPDYLAY_ERR_TEMPORAL_CALLBACK_FAILURE intentionally. */ + r = spdylay_session_add_rst_stream(session, data_frame->stream_id, + SPDYLAY_INTERNAL_ERROR); + spdylay_active_outbound_item_reset(&session->aob); + if(r != 0) { + return r; + } } else if(r < 0) { /* In this context, r is either SPDYLAY_ERR_NOMEM or SPDYLAY_ERR_CALLBACK_FAILURE */ @@ -2606,7 +2624,7 @@ ssize_t spdylay_session_pack_data(spdylay_session *session, r = frame->data_prd.read_callback (session, frame->stream_id, (*buf_ptr)+8, datamax, &eof, &frame->data_prd.source, session->user_data); - if(r == SPDYLAY_ERR_DEFERRED) { + if(r == SPDYLAY_ERR_DEFERRED || r == SPDYLAY_ERR_TEMPORAL_CALLBACK_FAILURE) { return r; } else if(r < 0 || datamax < (size_t)r) { /* This is the error code when callback is failed. */ diff --git a/lib/spdylay_session.h b/lib/spdylay_session.h index 8551023..ef72041 100644 --- a/lib/spdylay_session.h +++ b/lib/spdylay_session.h @@ -484,12 +484,14 @@ spdylay_stream* spdylay_session_get_stream(spdylay_session *session, * This function returns the size of packed frame if it succeeds, or * one of the following negative error codes: * - * SPDYLAY_ERR_NOMEM - * Out of memory. * SPDYLAY_ERR_DEFERRED * The DATA frame is postponed. + * SPDYLAY_ERR_TEMPORAL_CALLBACK_FAILURE + * The read_callback failed (stream error). + * SPDYLAY_ERR_NOMEM + * Out of memory. * SPDYLAY_ERR_CALLBACK_FAILURE - * The read_callback failed. + * The read_callback failed (session error). */ ssize_t spdylay_session_pack_data(spdylay_session *session, uint8_t **buf_ptr, size_t *buflen_ptr, diff --git a/tests/main.c b/tests/main.c index 429cd99..6a13127 100644 --- a/tests/main.c +++ b/tests/main.c @@ -156,6 +156,8 @@ int main(int argc, char* argv[]) test_spdylay_session_set_option) || !CU_add_test(pSuite, "submit_window_update", test_spdylay_submit_window_update) || + !CU_add_test(pSuite, "session_data_read_temporal_failure", + test_spdylay_session_data_read_temporal_failure) || !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 bc4422a..b6ccbbe 100644 --- a/tests/spdylay_session_test.c +++ b/tests/spdylay_session_test.c @@ -176,6 +176,22 @@ static ssize_t fixed_length_data_source_read_callback return wlen; } +static ssize_t temporal_failure_data_source_read_callback +(spdylay_session *session, int32_t stream_id, + uint8_t *buf, size_t len, int *eof, + spdylay_data_source *source, void *user_data) +{ + return SPDYLAY_ERR_TEMPORAL_CALLBACK_FAILURE; +} + +static ssize_t fail_data_source_read_callback +(spdylay_session *session, int32_t stream_id, + uint8_t *buf, size_t len, int *eof, + spdylay_data_source *source, void *user_data) +{ + return SPDYLAY_ERR_CALLBACK_FAILURE; +} + static void on_request_recv_callback(spdylay_session *session, int32_t stream_id, void *user_data) @@ -2328,3 +2344,58 @@ void test_spdylay_submit_window_update(void) spdylay_session_del(session); } + +void test_spdylay_session_data_read_temporal_failure(void) +{ + spdylay_session *session; + spdylay_session_callbacks callbacks; + const char *nv[] = { NULL }; + my_user_data ud; + spdylay_data_provider data_prd; + spdylay_frame frame; + spdylay_data *data_frame; + spdylay_stream *stream; + + memset(&callbacks, 0, sizeof(spdylay_session_callbacks)); + callbacks.send_callback = null_send_callback; + callbacks.on_ctrl_send_callback = on_ctrl_send_callback; + data_prd.read_callback = fixed_length_data_source_read_callback; + + ud.data_source_length = 128*1024; + + /* Initial window size is 64KiB */ + spdylay_session_client_new(&session, SPDYLAY_PROTO_SPDY3, &callbacks, &ud); + spdylay_submit_request(session, 3, nv, &data_prd, NULL); + + /* Sends 64KiB data */ + CU_ASSERT(0 == spdylay_session_send(session)); + CU_ASSERT(64*1024 == ud.data_source_length); + + stream = spdylay_session_get_stream(session, 1); + CU_ASSERT(NULL != stream->deferred_data); + CU_ASSERT(SPDYLAY_DATA == stream->deferred_data->frame_cat); + data_frame = (spdylay_data*)stream->deferred_data->frame; + data_frame->data_prd.read_callback = + temporal_failure_data_source_read_callback; + + /* Back 64KiB */ + spdylay_frame_window_update_init(&frame.window_update, SPDYLAY_PROTO_SPDY3, + 1, 64*1024); + spdylay_session_on_window_update_received(session, &frame); + spdylay_frame_window_update_free(&frame.window_update); + + /* Sending data will fail */ + ud.ctrl_send_cb_called = 0; + CU_ASSERT(0 == spdylay_session_send(session)); + CU_ASSERT(64*1024 == ud.data_source_length); + + CU_ASSERT(1 == ud.ctrl_send_cb_called); + CU_ASSERT(SPDYLAY_RST_STREAM == ud.sent_frame_type); + + data_prd.read_callback = fail_data_source_read_callback; + spdylay_submit_request(session, 3, nv, &data_prd, NULL); + /* Sending data will fail */ + CU_ASSERT(SPDYLAY_ERR_CALLBACK_FAILURE == spdylay_session_send(session)); + + spdylay_session_del(session); +} diff --git a/tests/spdylay_session_test.h b/tests/spdylay_session_test.h index 1ef3690..93b35c2 100644 --- a/tests/spdylay_session_test.h +++ b/tests/spdylay_session_test.h @@ -69,5 +69,6 @@ void test_spdylay_submit_syn_stream_with_credential(void); void test_spdylay_session_set_initial_client_cert_origin(void); void test_spdylay_session_set_option(void); void test_spdylay_submit_window_update(void); +void test_spdylay_session_data_read_temporal_failure(void); #endif /* SPDYLAY_SESSION_TEST_H */