From 21d76dcc75d5dfa20692bcd3eb9718973c54d8ef Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Sat, 28 Sep 2013 17:59:24 +0900 Subject: [PATCH 1/3] Add nghttp2_session_continue API function The NGHTTP2_ERR_PAUSE library error code is introduced to pause the execution of nghttp2_session_mem_recv() when that error code is returned from nghttp2_on_frame_recv_callback or nghttp2_on_data_chunk_recv_callback. If this happens, the parameters available for both callbacks are retained until the application calls nghttp2_session_continue(). The application must retain input bytes which was used to produce the frame. After successful call of nghttp2_session_continue, the application can continue to call nghttp2_session_mem_recv() to process additional data. --- lib/includes/nghttp2/nghttp2.h | 52 +++++- lib/nghttp2_session.c | 294 ++++++++++++++++++++++++--------- lib/nghttp2_session.h | 1 + tests/main.c | 1 + tests/nghttp2_session_test.c | 132 +++++++++++++++ tests/nghttp2_session_test.h | 1 + 6 files changed, 404 insertions(+), 77 deletions(-) diff --git a/lib/includes/nghttp2/nghttp2.h b/lib/includes/nghttp2/nghttp2.h index 646e7743..e6edd25b 100644 --- a/lib/includes/nghttp2/nghttp2.h +++ b/lib/includes/nghttp2/nghttp2.h @@ -258,6 +258,10 @@ typedef enum { * Insufficient buffer size given to function. */ NGHTTP2_ERR_INSUFF_BUFSIZE = -525, + /** + * Callback was paused by the application + */ + NGHTTP2_ERR_PAUSE = -526, /** * The errors < :enum:`NGHTTP2_ERR_FATAL` mean that the library is * under unexpected condition and cannot process any further data @@ -827,8 +831,19 @@ typedef ssize_t (*nghttp2_recv_callback) * argument passed in to the call to `nghttp2_session_client_new()` or * `nghttp2_session_server_new()`. * + * If the application uses `nghttp2_session_mem_recv()`, it can return + * :enum:`NGHTTP2_ERR_PAUSE` to make `nghttp2_session_mem_recv()` + * return without processing further input bytes. The |frame| + * parameter is retained until `nghttp2_session_continue()` is + * called. The application must retain the input bytes which was used + * to produce the |frame| parameter, because it may refer to the + * memory region included in the input bytes. The application which + * returns :enum:`NGHTTP2_ERR_PAUSE` must call + * `nghttp2_session_continue()` before `nghttp2_session_mem_recv()`. + * * The implementation of this function must return 0 if it - * succeeds. If nonzero is returned, it is treated as fatal error and + * succeeds. It may return :enum:`NGHTTP2_ERR_PAUSE`. If the other + * nonzero value is returned, it is treated as fatal error and * `nghttp2_session_recv()` and `nghttp2_session_send()` functions * immediately return :enum:`NGHTTP2_ERR_CALLBACK_FAILURE`. */ @@ -868,6 +883,16 @@ typedef int (*nghttp2_on_invalid_frame_recv_callback) * third argument passed in to the call to * `nghttp2_session_client_new()` or `nghttp2_session_server_new()`. * + * If the application uses `nghttp2_session_mem_recv()`, it can return + * :enum:`NGHTTP2_ERR_PAUSE` to make `nghttp2_session_mem_recv()` + * return without processing further input bytes. The |frame| + * parameter is retained until `nghttp2_session_continue()` is + * called. The application must retain the input bytes which was used + * to produce the |frame| parameter, because it may refer to the + * memory region included in the input bytes. The application which + * returns :enum:`NGHTTP2_ERR_PAUSE` must call + * `nghttp2_session_continue()` before `nghttp2_session_mem_recv()`. + * * The implementation of this function must return 0 if it * succeeds. If nonzero is returned, it is treated as fatal error and * `nghttp2_session_recv()` and `nghttp2_session_send()` functions @@ -1391,10 +1416,35 @@ int nghttp2_session_recv(nghttp2_session *session); * * :enum:`NGHTTP2_ERR_NOMEM` * Out of memory. + * :enum:`NGHTTP2_ERR_CALLBACK_FAILURE` + * The callback function failed. */ ssize_t nghttp2_session_mem_recv(nghttp2_session *session, const uint8_t *in, size_t inlen); +/** + * @function + * + * Perform post-processing after `nghttp2_session_mem_recv()` was + * paused by :enum:`NGHTTP2_ERR_PAUSE` from + * :member:`nghttp2_session_callbacks.on_frame_recv_callback` or + * :member:`nghttp2_session_callbacks.on_data_chunk_recv_callback`. + * + * If this function succeeds, the application can call + * `nghttp2_session_mem_recv()` again. + * + * 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_CALLBACK_FAILURE` + * The callback function failed. + * + */ +int nghttp2_session_continue(nghttp2_session *session); + /** * @function * diff --git a/lib/nghttp2_session.c b/lib/nghttp2_session.c index 07f25cf1..21dfdfb0 100644 --- a/lib/nghttp2_session.c +++ b/lib/nghttp2_session.c @@ -122,8 +122,39 @@ static int nghttp2_outbound_item_compar(const void *lhsx, const void *rhsx) } } -static void nghttp2_inbound_frame_reset(nghttp2_inbound_frame *iframe) +static void nghttp2_inbound_frame_reset(nghttp2_session *session) { + nghttp2_inbound_frame *iframe = &session->iframe; + if(iframe->error_code == NGHTTP2_ERR_PAUSE) { + switch(iframe->frame.hd.type) { + case NGHTTP2_HEADERS: + nghttp2_frame_headers_free(&iframe->frame.headers); + nghttp2_hd_end_headers(&session->hd_inflater); + break; + case NGHTTP2_PRIORITY: + nghttp2_frame_priority_free(&iframe->frame.priority); + break; + case NGHTTP2_RST_STREAM: + nghttp2_frame_rst_stream_free(&iframe->frame.rst_stream); + break; + case NGHTTP2_SETTINGS: + nghttp2_frame_settings_free(&iframe->frame.settings); + break; + case NGHTTP2_PUSH_PROMISE: + nghttp2_frame_push_promise_free(&iframe->frame.push_promise); + nghttp2_hd_end_headers(&session->hd_inflater); + break; + case NGHTTP2_PING: + nghttp2_frame_ping_free(&iframe->frame.ping); + break; + case NGHTTP2_GOAWAY: + nghttp2_frame_goaway_free(&iframe->frame.goaway); + break; + case NGHTTP2_WINDOW_UPDATE: + nghttp2_frame_window_update_free(&iframe->frame.window_update); + break; + } + } iframe->state = NGHTTP2_RECV_HEAD; iframe->payloadlen = iframe->buflen = iframe->off = 0; iframe->headbufoff = 0; @@ -214,7 +245,7 @@ static int nghttp2_session_new(nghttp2_session **session_ptr, } (*session_ptr)->iframe.bufmax = NGHTTP2_INITIAL_INBOUND_FRAMEBUF_LENGTH; - nghttp2_inbound_frame_reset(&(*session_ptr)->iframe); + nghttp2_inbound_frame_reset(*session_ptr); return 0; @@ -302,6 +333,7 @@ void nghttp2_session_del(nghttp2_session *session) if(session == NULL) { return; } + nghttp2_inbound_frame_reset(session); nghttp2_map_each_free(&session->streams, nghttp2_free_streams, NULL); nghttp2_session_ob_pq_free(&session->ob_pq); nghttp2_session_ob_pq_free(&session->ob_ss_pq); @@ -1627,6 +1659,9 @@ static int nghttp2_session_call_on_frame_received if(session->callbacks.on_frame_recv_callback) { rv = session->callbacks.on_frame_recv_callback(session, frame, session->user_data); + if(rv == NGHTTP2_ERR_PAUSE) { + return rv; + } if(rv != 0) { return NGHTTP2_ERR_CALLBACK_FAILURE; } @@ -1771,14 +1806,12 @@ int nghttp2_session_on_request_headers_received(nghttp2_session *session, if(!stream) { return NGHTTP2_ERR_NOMEM; } - if(frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { - nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_RD); - } rv = nghttp2_session_call_on_frame_received(session, frame); if(rv != 0) { return rv; } if(frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { + nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_RD); rv = nghttp2_session_call_on_request_recv(session, frame->hd.stream_id); } /* Here we assume that stream is not shutdown in NGHTTP2_SHUT_WR */ @@ -1855,7 +1888,20 @@ int nghttp2_session_on_push_response_headers_received(nghttp2_session *session, } nghttp2_stream_promise_fulfilled(stream); ++session->num_incoming_streams; - return nghttp2_session_call_on_frame_received(session, frame); + rv = nghttp2_session_call_on_frame_received(session, frame); + if(rv != 0) { + return rv; + } + if(frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { + /* This is the last frame of this stream, so disallow further + receptions. */ + nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_RD); + rv = nghttp2_session_close_stream_if_shut_rdwr(session, stream); + if(rv != 0 && nghttp2_is_fatal(rv)) { + return rv; + } + } + return 0; } int nghttp2_session_on_headers_received(nghttp2_session *session, @@ -2412,17 +2458,15 @@ static int session_on_connection_window_update_received (session, frame, NGHTTP2_FLOW_CONTROL_ERROR); } session->remote_window_size += frame->window_update.window_size_increment; - rv = nghttp2_session_call_on_frame_received(session, frame); - if(rv != 0) { - return rv; - } /* To queue the DATA deferred by connection-level flow-control, we have to check all streams. Bad. */ if(session->remote_window_size > 0) { - return nghttp2_session_push_back_deferred_data(session); - } else { - return 0; + rv = nghttp2_session_push_back_deferred_data(session); + if(rv != 0) { + return 0; + } } + return nghttp2_session_call_on_frame_received(session, frame); } static int session_on_stream_window_update_received @@ -2484,17 +2528,18 @@ static int get_error_code_from_lib_error_code(int lib_error_code) } } -/* For errors, this function only returns FATAL error. */ +/* For errors, this function returns NGHTTP2_ERR_PAUSE, or FATAL + ones. */ static int nghttp2_session_process_ctrl_frame(nghttp2_session *session) { int r = 0; uint16_t type; - nghttp2_frame frame; + nghttp2_frame *frame = &session->iframe.frame; type = session->iframe.headbuf[2]; switch(type) { case NGHTTP2_HEADERS: if(session->iframe.error_code == 0) { - r = nghttp2_frame_unpack_headers(&frame.headers, + r = nghttp2_frame_unpack_headers(&frame->headers, session->iframe.headbuf, sizeof(session->iframe.headbuf), session->iframe.buf, @@ -2502,7 +2547,7 @@ static int nghttp2_session_process_ctrl_frame(nghttp2_session *session) &session->hd_inflater); } else if(session->iframe.error_code == NGHTTP2_ERR_FRAME_TOO_LARGE) { r = nghttp2_frame_unpack_headers_without_nv - (&frame.headers, + (&frame->headers, session->iframe.headbuf, sizeof(session->iframe.headbuf), session->iframe.buf, session->iframe.buflen); if(r == 0) { @@ -2513,74 +2558,82 @@ static int nghttp2_session_process_ctrl_frame(nghttp2_session *session) } if(r == 0) { nghttp2_stream *stream; - stream = nghttp2_session_get_stream(session, frame.hd.stream_id); + stream = nghttp2_session_get_stream(session, frame->hd.stream_id); if(stream) { - if(nghttp2_session_is_my_stream_id(session, frame.hd.stream_id)) { + if(nghttp2_session_is_my_stream_id(session, frame->hd.stream_id)) { if(stream->state == NGHTTP2_STREAM_OPENING) { - frame.headers.cat = NGHTTP2_HCAT_RESPONSE; + frame->headers.cat = NGHTTP2_HCAT_RESPONSE; r = nghttp2_session_on_response_headers_received - (session, &frame, stream); + (session, frame, stream); } else { - frame.headers.cat = NGHTTP2_HCAT_HEADERS; - r = nghttp2_session_on_headers_received(session, &frame, stream); + frame->headers.cat = NGHTTP2_HCAT_HEADERS; + r = nghttp2_session_on_headers_received(session, frame, stream); } } else if(!session->server && stream->state == NGHTTP2_STREAM_RESERVED) { - frame.headers.cat = NGHTTP2_HCAT_PUSH_RESPONSE; + frame->headers.cat = NGHTTP2_HCAT_PUSH_RESPONSE; r = nghttp2_session_on_push_response_headers_received - (session, &frame, stream); + (session, frame, stream); } else { - frame.headers.cat = NGHTTP2_HCAT_HEADERS; - r = nghttp2_session_on_headers_received(session, &frame, stream); + frame->headers.cat = NGHTTP2_HCAT_HEADERS; + r = nghttp2_session_on_headers_received(session, frame, stream); } } else { - frame.headers.cat = NGHTTP2_HCAT_REQUEST; - r = nghttp2_session_on_request_headers_received(session, &frame); + frame->headers.cat = NGHTTP2_HCAT_REQUEST; + r = nghttp2_session_on_request_headers_received(session, frame); + } + if(r != NGHTTP2_ERR_PAUSE) { + nghttp2_frame_headers_free(&frame->headers); + nghttp2_hd_end_headers(&session->hd_inflater); } - nghttp2_frame_headers_free(&frame.headers); - nghttp2_hd_end_headers(&session->hd_inflater); } else if(nghttp2_is_non_fatal(r)) { r = nghttp2_session_handle_parse_error (session, type, r, get_error_code_from_lib_error_code(r)); } break; case NGHTTP2_PRIORITY: - r = nghttp2_frame_unpack_priority(&frame.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); + r = nghttp2_session_on_priority_received(session, frame); + if(r != NGHTTP2_ERR_PAUSE) { + 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, + r = nghttp2_frame_unpack_rst_stream(&frame->rst_stream, session->iframe.headbuf, sizeof(session->iframe.headbuf), session->iframe.buf, session->iframe.buflen); if(r == 0) { - r = nghttp2_session_on_rst_stream_received(session, &frame); - nghttp2_frame_rst_stream_free(&frame.rst_stream); + r = nghttp2_session_on_rst_stream_received(session, frame); + if(r != NGHTTP2_ERR_PAUSE) { + nghttp2_frame_rst_stream_free(&frame->rst_stream); + } } else if(nghttp2_is_non_fatal(r)) { r = nghttp2_session_handle_parse_error(session, type, r, NGHTTP2_PROTOCOL_ERROR); } break; case NGHTTP2_SETTINGS: - r = nghttp2_frame_unpack_settings(&frame.settings, + r = nghttp2_frame_unpack_settings(&frame->settings, session->iframe.headbuf, sizeof(session->iframe.headbuf), session->iframe.buf, session->iframe.buflen); if(r == 0) { - r = nghttp2_session_on_settings_received(session, &frame); - nghttp2_frame_settings_free(&frame.settings); + r = nghttp2_session_on_settings_received(session, frame); + if(r != NGHTTP2_ERR_PAUSE) { + nghttp2_frame_settings_free(&frame->settings); + } } else if(nghttp2_is_non_fatal(r)) { r = nghttp2_session_handle_parse_error(session, type, r, NGHTTP2_PROTOCOL_ERROR); @@ -2588,7 +2641,7 @@ static int nghttp2_session_process_ctrl_frame(nghttp2_session *session) break; case NGHTTP2_PUSH_PROMISE: if(session->iframe.error_code == 0) { - r = nghttp2_frame_unpack_push_promise(&frame.push_promise, + r = nghttp2_frame_unpack_push_promise(&frame->push_promise, session->iframe.headbuf, sizeof(session->iframe.headbuf), session->iframe.buf, @@ -2598,51 +2651,59 @@ static int nghttp2_session_process_ctrl_frame(nghttp2_session *session) r = session->iframe.error_code; } if(r == 0) { - r = nghttp2_session_on_push_promise_received(session, &frame); - nghttp2_frame_push_promise_free(&frame.push_promise); - nghttp2_hd_end_headers(&session->hd_inflater); + r = nghttp2_session_on_push_promise_received(session, frame); + if(r != NGHTTP2_ERR_PAUSE) { + nghttp2_frame_push_promise_free(&frame->push_promise); + nghttp2_hd_end_headers(&session->hd_inflater); + } } else if(nghttp2_is_non_fatal(r)) { r = nghttp2_session_handle_parse_error (session, type, r, get_error_code_from_lib_error_code(r)); } break; case NGHTTP2_PING: - r = nghttp2_frame_unpack_ping(&frame.ping, + r = nghttp2_frame_unpack_ping(&frame->ping, session->iframe.headbuf, sizeof(session->iframe.headbuf), session->iframe.buf, session->iframe.buflen); if(r == 0) { - r = nghttp2_session_on_ping_received(session, &frame); - nghttp2_frame_ping_free(&frame.ping); + r = nghttp2_session_on_ping_received(session, frame); + if(r != NGHTTP2_ERR_PAUSE) { + nghttp2_frame_ping_free(&frame->ping); + } } else if(nghttp2_is_non_fatal(r)) { r = nghttp2_session_handle_parse_error(session, type, r, NGHTTP2_PROTOCOL_ERROR); } break; case NGHTTP2_GOAWAY: - r = nghttp2_frame_unpack_goaway(&frame.goaway, + r = nghttp2_frame_unpack_goaway(&frame->goaway, session->iframe.headbuf, sizeof(session->iframe.headbuf), session->iframe.buf, session->iframe.buflen); if(r == 0) { - r = nghttp2_session_on_goaway_received(session, &frame); - nghttp2_frame_goaway_free(&frame.goaway); + r = nghttp2_session_on_goaway_received(session, frame); + if(r != NGHTTP2_ERR_PAUSE) { + nghttp2_frame_goaway_free(&frame->goaway); + } } else if(nghttp2_is_non_fatal(r)) { r = nghttp2_session_handle_parse_error(session, type, r, NGHTTP2_PROTOCOL_ERROR); } break; case NGHTTP2_WINDOW_UPDATE: - r = nghttp2_frame_unpack_window_update(&frame.window_update, + r = nghttp2_frame_unpack_window_update(&frame->window_update, session->iframe.headbuf, sizeof(session->iframe.headbuf), session->iframe.buf, session->iframe.buflen); if(r == 0) { - r = nghttp2_session_on_window_update_received(session, &frame); - nghttp2_frame_window_update_free(&frame.window_update); + r = nghttp2_session_on_window_update_received(session, frame); + if(r != NGHTTP2_ERR_PAUSE) { + nghttp2_frame_window_update_free(&frame->window_update); + } } else if(nghttp2_is_non_fatal(r)) { r = nghttp2_session_handle_parse_error(session, type, r, NGHTTP2_PROTOCOL_ERROR); @@ -2662,7 +2723,10 @@ static int nghttp2_session_process_ctrl_frame(nghttp2_session *session) } } } - if(nghttp2_is_fatal(r)) { + if(r == NGHTTP2_ERR_PAUSE) { + session->iframe.error_code = NGHTTP2_ERR_PAUSE; + return r; + } else if(nghttp2_is_fatal(r)) { return r; } else { return 0; @@ -2884,6 +2948,73 @@ static int nghttp2_session_check_data_recv_allowed(nghttp2_session *session, return 0; } +int nghttp2_session_continue(nghttp2_session *session) +{ + nghttp2_frame *frame = &session->iframe.frame; + nghttp2_stream *stream; + int rv = 0; + if(session->iframe.error_code != NGHTTP2_ERR_PAUSE) { + return 0; + } + switch(frame->hd.type) { + case NGHTTP2_DATA: + /* To call on_data_recv_callback */ + return nghttp2_session_mem_recv(session, NULL, 0); + case NGHTTP2_HEADERS: + switch(frame->headers.cat) { + case NGHTTP2_HCAT_REQUEST: + if(frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { + rv = nghttp2_session_call_on_request_recv(session, frame->hd.stream_id); + stream = nghttp2_session_get_stream(session, frame->hd.stream_id); + nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_RD); + } + break; + case NGHTTP2_HCAT_RESPONSE: + case NGHTTP2_HCAT_PUSH_RESPONSE: + if(frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { + stream = nghttp2_session_get_stream(session, frame->hd.stream_id); + /* This is the last frame of this stream, so disallow + further receptions. */ + nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_RD); + rv = nghttp2_session_close_stream_if_shut_rdwr(session, stream); + if(!nghttp2_is_fatal(rv)) { + rv = 0; + } + } + break; + case NGHTTP2_HCAT_HEADERS: + if(frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { + if(!nghttp2_session_is_my_stream_id(session, frame->hd.stream_id)) { + rv = nghttp2_session_call_on_request_recv(session, + frame->hd.stream_id); + if(rv != 0) { + break; + } + } + stream = nghttp2_session_get_stream(session, frame->hd.stream_id); + nghttp2_stream_shutdown(stream, NGHTTP2_SHUT_RD); + rv = nghttp2_session_close_stream_if_shut_rdwr(session, stream); + if(!nghttp2_is_fatal(rv)) { + rv = 0; + } + } + break; + } + break; + case NGHTTP2_RST_STREAM: + rv = nghttp2_session_close_stream(session, frame->hd.stream_id, + frame->rst_stream.error_code); + if(!nghttp2_is_fatal(rv)) { + rv = 0; + } + break; + default: + break; + } + nghttp2_inbound_frame_reset(session); + return rv; +} + ssize_t nghttp2_session_mem_recv(nghttp2_session *session, const uint8_t *in, size_t inlen) { @@ -2968,27 +3099,6 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session, memcpy(session->iframe.buf+session->iframe.off, inmark, readlen); } } else { - /* For data frame, We don't buffer data. Instead, just pass - received data to callback function. */ - data_stream_id = nghttp2_get_uint32(&session->iframe.headbuf[4]) & - NGHTTP2_STREAM_ID_MASK; - data_flags = session->iframe.headbuf[3]; - if(session->iframe.state != NGHTTP2_RECV_PAYLOAD_IGN) { - if(session->callbacks.on_data_chunk_recv_callback) { - if(session->callbacks.on_data_chunk_recv_callback - (session, - data_flags, - data_stream_id, - inmark, - readlen, - session->user_data) != 0) { - return NGHTTP2_ERR_CALLBACK_FAILURE; - } - } - } - /* TODO We need on_ignored_data_chunk_recv_callback, for - ingored DATA frame, so that the application can calculate - connection-level window size. */ } session->iframe.off += readlen; inmark += readlen; @@ -3020,6 +3130,35 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session, } } } + /* For data frame, We don't buffer data. Instead, just pass + received data to callback function. */ + data_stream_id = nghttp2_get_uint32(&session->iframe.headbuf[4]) & + NGHTTP2_STREAM_ID_MASK; + data_flags = session->iframe.headbuf[3]; + if(session->iframe.state != NGHTTP2_RECV_PAYLOAD_IGN) { + if(session->callbacks.on_data_chunk_recv_callback) { + r = session->callbacks.on_data_chunk_recv_callback + (session, + data_flags, + data_stream_id, + inmark - readlen, + readlen, + session->user_data); + if(r == NGHTTP2_ERR_PAUSE) { + /* Set type to NGHTTP2_DATA, so that we can see what was + paused in nghttp2_session_continue() */ + session->iframe.error_code = NGHTTP2_ERR_PAUSE; + session->iframe.frame.hd.type = NGHTTP2_DATA; + return inmark - in; + } + if(r != 0) { + return NGHTTP2_ERR_CALLBACK_FAILURE; + } + } + } + /* TODO We need on_ignored_data_chunk_recv_callback, for + ingored DATA frame, so that the application can calculate + connection-level window size. */ } if(session->iframe.payloadlen == session->iframe.off) { if(session->iframe.error_code != NGHTTP2_ERR_FRAME_TOO_LARGE) { @@ -3030,13 +3169,16 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session, } else { r = nghttp2_session_process_data_frame(session); } + if(r == NGHTTP2_ERR_PAUSE) { + return inmark - in; + } if(r < 0) { /* FATAL */ assert(r < NGHTTP2_ERR_FATAL); return r; } } - nghttp2_inbound_frame_reset(&session->iframe); + nghttp2_inbound_frame_reset(session); } } } diff --git a/lib/nghttp2_session.h b/lib/nghttp2_session.h index 97e37836..f7bda56f 100644 --- a/lib/nghttp2_session.h +++ b/lib/nghttp2_session.h @@ -80,6 +80,7 @@ typedef enum { } nghttp2_inbound_state; typedef struct { + nghttp2_frame frame; nghttp2_inbound_state state; uint8_t headbuf[NGHTTP2_FRAME_HEAD_LENGTH]; /* How many bytes are filled in headbuf */ diff --git a/tests/main.c b/tests/main.c index 516345ed..529df8b2 100644 --- a/tests/main.c +++ b/tests/main.c @@ -86,6 +86,7 @@ int main(int argc, char* argv[]) test_nghttp2_session_recv_data) || !CU_add_test(pSuite, "session_recv_frame_too_large", test_nghttp2_session_recv_frame_too_large) || + !CU_add_test(pSuite, "session_continue", test_nghttp2_session_continue) || !CU_add_test(pSuite, "session_add_frame", test_nghttp2_session_add_frame) || !CU_add_test(pSuite, "session_on_request_headers_received", diff --git a/tests/nghttp2_session_test.c b/tests/nghttp2_session_test.c index df5648ba..388960c3 100644 --- a/tests/nghttp2_session_test.c +++ b/tests/nghttp2_session_test.c @@ -67,6 +67,7 @@ typedef struct { size_t block_count; int data_chunk_recv_cb_called; int data_recv_cb_called; + const nghttp2_frame *frame; } my_user_data; static void scripted_data_feed_init(scripted_data_feed *df, @@ -135,6 +136,16 @@ static int on_frame_recv_callback(nghttp2_session *session, return 0; } +static int pause_on_frame_recv_callback(nghttp2_session *session, + const nghttp2_frame *frame, + void *user_data) +{ + my_user_data *ud = (my_user_data*)user_data; + ++ud->frame_recv_cb_called; + ud->frame = frame; + return NGHTTP2_ERR_PAUSE; +} + static int on_invalid_frame_recv_callback(nghttp2_session *session, const nghttp2_frame *frame, nghttp2_error_code error_code, @@ -177,6 +188,16 @@ static int on_data_chunk_recv_callback(nghttp2_session *session, return 0; } +static int pause_on_data_chunk_recv_callback(nghttp2_session *session, + uint8_t flags, int32_t stream_id, + const uint8_t *data, size_t len, + void *user_data) +{ + my_user_data *ud = (my_user_data*)user_data; + ++ud->data_chunk_recv_cb_called; + return NGHTTP2_ERR_PAUSE; +} + static int on_data_recv_callback(nghttp2_session *session, uint16_t length, uint8_t flags, int32_t stream_id, void *user_data) @@ -631,6 +652,117 @@ void test_nghttp2_session_recv_data(void) nghttp2_session_del(session); } +void test_nghttp2_session_continue(void) +{ + nghttp2_session *session; + nghttp2_session_callbacks callbacks; + my_user_data user_data; + const char *nv1[] = { + "url", "/", NULL + }; + const char *nv2[] = { + "user-agent", "nghttp2/1.0.0", NULL + }; + uint8_t *framedata = NULL; + size_t framedatalen = 0; + ssize_t framelen1, framelen2; + ssize_t rv; + uint8_t buffer[4096]; + size_t buflen; + nghttp2_frame frame; + nghttp2_nv *nva; + ssize_t nvlen; + const nghttp2_frame *recv_frame; + nghttp2_nv nvcheck; + nghttp2_frame_hd data_hd; + + memset(&callbacks, 0, sizeof(nghttp2_session_callbacks)); + callbacks.send_callback = null_send_callback; + callbacks.on_frame_recv_callback = pause_on_frame_recv_callback; + callbacks.on_data_chunk_recv_callback = pause_on_data_chunk_recv_callback; + callbacks.on_data_recv_callback = on_data_recv_callback; + + nghttp2_session_server_new(&session, &callbacks, &user_data); + + /* Make 2 HEADERS frames */ + nvlen = nghttp2_nv_array_from_cstr(&nva, nv1); + nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_END_HEADERS, + 1, NGHTTP2_PRI_DEFAULT, nva, nvlen); + framelen1 = nghttp2_frame_pack_headers(&framedata, &framedatalen, + &frame.headers, + &session->hd_deflater); + nghttp2_frame_headers_free(&frame.headers); + + nghttp2_hd_end_headers(&session->hd_deflater); + memcpy(buffer, framedata, framelen1); + + nvlen = nghttp2_nv_array_from_cstr(&nva, nv2); + nghttp2_frame_headers_init(&frame.headers, NGHTTP2_FLAG_END_HEADERS, + 3, NGHTTP2_PRI_DEFAULT, nva, nvlen); + framelen2 = nghttp2_frame_pack_headers(&framedata, &framedatalen, + &frame.headers, + &session->hd_deflater); + nghttp2_frame_headers_free(&frame.headers); + + nghttp2_hd_end_headers(&session->hd_deflater); + memcpy(buffer + framelen1, framedata, framelen2); + buflen = framelen1 + framelen2; + + /* Receive 1st HEADERS and pause */ + rv = nghttp2_session_mem_recv(session, buffer, buflen); + CU_ASSERT(rv == framelen1); + + recv_frame = user_data.frame; + CU_ASSERT(NGHTTP2_HEADERS == recv_frame->hd.type); + CU_ASSERT(framelen1 - NGHTTP2_FRAME_HEAD_LENGTH == recv_frame->hd.length); + CU_ASSERT(1 == recv_frame->headers.nvlen); + nvcheck.name = (uint8_t*)nv1[0]; + nvcheck.namelen = strlen(nv1[0]); + nvcheck.value = (uint8_t*)nv1[1]; + nvcheck.valuelen = strlen(nv1[1]); + CU_ASSERT(nghttp2_nv_equal(&nvcheck, recv_frame->headers.nva)); + + rv = nghttp2_session_continue(session); + CU_ASSERT(rv == 0); + + /* Receive 2nd HEADERS and pause */ + rv = nghttp2_session_mem_recv(session, buffer + framelen1, + buflen - framelen1); + CU_ASSERT(rv == framelen2); + + recv_frame = user_data.frame; + CU_ASSERT(NGHTTP2_HEADERS == recv_frame->hd.type); + CU_ASSERT(framelen2 - NGHTTP2_FRAME_HEAD_LENGTH == recv_frame->hd.length); + CU_ASSERT(1 == recv_frame->headers.nvlen); + nvcheck.name = (uint8_t*)nv2[0]; + nvcheck.namelen = strlen(nv2[0]); + nvcheck.value = (uint8_t*)nv2[1]; + nvcheck.valuelen = strlen(nv2[1]); + CU_ASSERT(nghttp2_nv_equal(&nvcheck, recv_frame->headers.nva)); + + rv = nghttp2_session_continue(session); + CU_ASSERT(rv == 0); + + /* Receive DATA */ + data_hd.length = 16; + data_hd.type = NGHTTP2_DATA; + data_hd.flags = NGHTTP2_FLAG_NONE; + data_hd.stream_id = 1; + nghttp2_frame_pack_frame_hd(buffer, &data_hd); + /* Intentionally specify larger buffer size to see pause is kicked + in. */ + rv = nghttp2_session_mem_recv(session, buffer, sizeof(buffer)); + CU_ASSERT(16 + NGHTTP2_FRAME_HEAD_LENGTH == rv); + + user_data.data_recv_cb_called = 0; + rv = nghttp2_session_continue(session); + CU_ASSERT(rv == 0); + CU_ASSERT(1 == user_data.data_recv_cb_called); + + free(framedata); + nghttp2_session_del(session); +} + void test_nghttp2_session_add_frame(void) { nghttp2_session *session; diff --git a/tests/nghttp2_session_test.h b/tests/nghttp2_session_test.h index fcbff56b..6ddb7ffb 100644 --- a/tests/nghttp2_session_test.h +++ b/tests/nghttp2_session_test.h @@ -31,6 +31,7 @@ void test_nghttp2_session_recv_invalid_frame(void); void test_nghttp2_session_recv_eof(void); void test_nghttp2_session_recv_data(void); void test_nghttp2_session_recv_frame_too_large(void); +void test_nghttp2_session_continue(void); void test_nghttp2_session_add_frame(void); void test_nghttp2_session_on_request_headers_received(void); void test_nghttp2_session_on_response_headers_received(void); From f30a238e4122959a38fe13f480e12c7101f5a6a3 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Sat, 28 Sep 2013 18:14:01 +0900 Subject: [PATCH 2/3] Update doc --- lib/includes/nghttp2/nghttp2.h | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/lib/includes/nghttp2/nghttp2.h b/lib/includes/nghttp2/nghttp2.h index e6edd25b..088f19ef 100644 --- a/lib/includes/nghttp2/nghttp2.h +++ b/lib/includes/nghttp2/nghttp2.h @@ -1409,7 +1409,13 @@ int nghttp2_session_recv(nghttp2_session *session); * `nghttp2_session_recv()`. * * In the current implementation, this function always tries to - * processes all input data unless an error occurs. + * processes all input data unless either an error occurs or + * :enum:`NGHTTP2_ERR_PAUSE` is returned from + * :member:`nghttp2_session_callbacks.on_frame_recv_callback` or + * :member:`nghttp2_session_callbacks.on_data_chunk_recv_callback`. + * If :enum:`NGHTTP2_ERR_PAUSE` is used, the return value includes the + * number of bytes which was used to produce the data or frame for the + * callback. * * This function returns the number of processed bytes, or one of the * following negative error codes: @@ -1430,6 +1436,10 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session, * :member:`nghttp2_session_callbacks.on_frame_recv_callback` or * :member:`nghttp2_session_callbacks.on_data_chunk_recv_callback`. * + * This function frees resources associated with paused frames. It + * may also call additional callbacks, such as + * :member:`nghttp2_session_callbacks.on_stream_close_callback`. + * * If this function succeeds, the application can call * `nghttp2_session_mem_recv()` again. * From bddb4de9469baa50f70dfa3c3c7d49c85345ff42 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Thu, 3 Oct 2013 22:51:58 +0900 Subject: [PATCH 3/3] Fix data_stream_id and data_flags are not assigned --- lib/nghttp2_session.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/nghttp2_session.c b/lib/nghttp2_session.c index 21dfdfb0..1f39eb44 100644 --- a/lib/nghttp2_session.c +++ b/lib/nghttp2_session.c @@ -3099,6 +3099,9 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session, memcpy(session->iframe.buf+session->iframe.off, inmark, readlen); } } else { + data_stream_id = nghttp2_get_uint32(&session->iframe.headbuf[4]) & + NGHTTP2_STREAM_ID_MASK; + data_flags = session->iframe.headbuf[3]; } session->iframe.off += readlen; inmark += readlen; @@ -3132,9 +3135,6 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session, } /* For data frame, We don't buffer data. Instead, just pass received data to callback function. */ - data_stream_id = nghttp2_get_uint32(&session->iframe.headbuf[4]) & - NGHTTP2_STREAM_ID_MASK; - data_flags = session->iframe.headbuf[3]; if(session->iframe.state != NGHTTP2_RECV_PAYLOAD_IGN) { if(session->callbacks.on_data_chunk_recv_callback) { r = session->callbacks.on_data_chunk_recv_callback