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.
This commit is contained in:
Tatsuhiro Tsujikawa 2013-09-28 17:59:24 +09:00
parent bfe7a9af00
commit 21d76dcc75
6 changed files with 404 additions and 77 deletions

View File

@ -258,6 +258,10 @@ typedef enum {
* Insufficient buffer size given to function. * Insufficient buffer size given to function.
*/ */
NGHTTP2_ERR_INSUFF_BUFSIZE = -525, 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 * The errors < :enum:`NGHTTP2_ERR_FATAL` mean that the library is
* under unexpected condition and cannot process any further data * 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 * argument passed in to the call to `nghttp2_session_client_new()` or
* `nghttp2_session_server_new()`. * `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 * 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 * `nghttp2_session_recv()` and `nghttp2_session_send()` functions
* immediately return :enum:`NGHTTP2_ERR_CALLBACK_FAILURE`. * 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 * third argument passed in to the call to
* `nghttp2_session_client_new()` or `nghttp2_session_server_new()`. * `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 * The implementation of this function must return 0 if it
* succeeds. If nonzero is returned, it is treated as fatal error and * succeeds. If nonzero is returned, it is treated as fatal error and
* `nghttp2_session_recv()` and `nghttp2_session_send()` functions * `nghttp2_session_recv()` and `nghttp2_session_send()` functions
@ -1391,10 +1416,35 @@ int nghttp2_session_recv(nghttp2_session *session);
* *
* :enum:`NGHTTP2_ERR_NOMEM` * :enum:`NGHTTP2_ERR_NOMEM`
* Out of memory. * Out of memory.
* :enum:`NGHTTP2_ERR_CALLBACK_FAILURE`
* The callback function failed.
*/ */
ssize_t nghttp2_session_mem_recv(nghttp2_session *session, ssize_t nghttp2_session_mem_recv(nghttp2_session *session,
const uint8_t *in, size_t inlen); 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 * @function
* *

View File

@ -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->state = NGHTTP2_RECV_HEAD;
iframe->payloadlen = iframe->buflen = iframe->off = 0; iframe->payloadlen = iframe->buflen = iframe->off = 0;
iframe->headbufoff = 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; (*session_ptr)->iframe.bufmax = NGHTTP2_INITIAL_INBOUND_FRAMEBUF_LENGTH;
nghttp2_inbound_frame_reset(&(*session_ptr)->iframe); nghttp2_inbound_frame_reset(*session_ptr);
return 0; return 0;
@ -302,6 +333,7 @@ void nghttp2_session_del(nghttp2_session *session)
if(session == NULL) { if(session == NULL) {
return; return;
} }
nghttp2_inbound_frame_reset(session);
nghttp2_map_each_free(&session->streams, nghttp2_free_streams, NULL); 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_pq);
nghttp2_session_ob_pq_free(&session->ob_ss_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) { if(session->callbacks.on_frame_recv_callback) {
rv = session->callbacks.on_frame_recv_callback(session, frame, rv = session->callbacks.on_frame_recv_callback(session, frame,
session->user_data); session->user_data);
if(rv == NGHTTP2_ERR_PAUSE) {
return rv;
}
if(rv != 0) { if(rv != 0) {
return NGHTTP2_ERR_CALLBACK_FAILURE; return NGHTTP2_ERR_CALLBACK_FAILURE;
} }
@ -1771,14 +1806,12 @@ int nghttp2_session_on_request_headers_received(nghttp2_session *session,
if(!stream) { if(!stream) {
return NGHTTP2_ERR_NOMEM; 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); rv = nghttp2_session_call_on_frame_received(session, frame);
if(rv != 0) { if(rv != 0) {
return rv; return rv;
} }
if(frame->hd.flags & NGHTTP2_FLAG_END_STREAM) { 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); rv = nghttp2_session_call_on_request_recv(session, frame->hd.stream_id);
} }
/* Here we assume that stream is not shutdown in NGHTTP2_SHUT_WR */ /* 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); nghttp2_stream_promise_fulfilled(stream);
++session->num_incoming_streams; ++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, 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, frame, NGHTTP2_FLOW_CONTROL_ERROR);
} }
session->remote_window_size += frame->window_update.window_size_increment; 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 /* To queue the DATA deferred by connection-level flow-control, we
have to check all streams. Bad. */ have to check all streams. Bad. */
if(session->remote_window_size > 0) { if(session->remote_window_size > 0) {
return nghttp2_session_push_back_deferred_data(session); rv = nghttp2_session_push_back_deferred_data(session);
} else { if(rv != 0) {
return 0; return 0;
}
} }
return nghttp2_session_call_on_frame_received(session, frame);
} }
static int session_on_stream_window_update_received 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) static int nghttp2_session_process_ctrl_frame(nghttp2_session *session)
{ {
int r = 0; int r = 0;
uint16_t type; uint16_t type;
nghttp2_frame frame; nghttp2_frame *frame = &session->iframe.frame;
type = session->iframe.headbuf[2]; type = session->iframe.headbuf[2];
switch(type) { switch(type) {
case NGHTTP2_HEADERS: case NGHTTP2_HEADERS:
if(session->iframe.error_code == 0) { if(session->iframe.error_code == 0) {
r = nghttp2_frame_unpack_headers(&frame.headers, r = nghttp2_frame_unpack_headers(&frame->headers,
session->iframe.headbuf, session->iframe.headbuf,
sizeof(session->iframe.headbuf), sizeof(session->iframe.headbuf),
session->iframe.buf, session->iframe.buf,
@ -2502,7 +2547,7 @@ static int nghttp2_session_process_ctrl_frame(nghttp2_session *session)
&session->hd_inflater); &session->hd_inflater);
} else if(session->iframe.error_code == NGHTTP2_ERR_FRAME_TOO_LARGE) { } else if(session->iframe.error_code == NGHTTP2_ERR_FRAME_TOO_LARGE) {
r = nghttp2_frame_unpack_headers_without_nv r = nghttp2_frame_unpack_headers_without_nv
(&frame.headers, (&frame->headers,
session->iframe.headbuf, sizeof(session->iframe.headbuf), session->iframe.headbuf, sizeof(session->iframe.headbuf),
session->iframe.buf, session->iframe.buflen); session->iframe.buf, session->iframe.buflen);
if(r == 0) { if(r == 0) {
@ -2513,74 +2558,82 @@ static int nghttp2_session_process_ctrl_frame(nghttp2_session *session)
} }
if(r == 0) { if(r == 0) {
nghttp2_stream *stream; 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(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) { 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 r = nghttp2_session_on_response_headers_received
(session, &frame, stream); (session, frame, stream);
} else { } else {
frame.headers.cat = NGHTTP2_HCAT_HEADERS; frame->headers.cat = NGHTTP2_HCAT_HEADERS;
r = nghttp2_session_on_headers_received(session, &frame, stream); r = nghttp2_session_on_headers_received(session, frame, stream);
} }
} else if(!session->server && } else if(!session->server &&
stream->state == NGHTTP2_STREAM_RESERVED) { 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 r = nghttp2_session_on_push_response_headers_received
(session, &frame, stream); (session, frame, stream);
} else { } else {
frame.headers.cat = NGHTTP2_HCAT_HEADERS; frame->headers.cat = NGHTTP2_HCAT_HEADERS;
r = nghttp2_session_on_headers_received(session, &frame, stream); r = nghttp2_session_on_headers_received(session, frame, stream);
} }
} else { } else {
frame.headers.cat = NGHTTP2_HCAT_REQUEST; frame->headers.cat = NGHTTP2_HCAT_REQUEST;
r = nghttp2_session_on_request_headers_received(session, &frame); 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)) { } else if(nghttp2_is_non_fatal(r)) {
r = nghttp2_session_handle_parse_error r = nghttp2_session_handle_parse_error
(session, type, r, get_error_code_from_lib_error_code(r)); (session, type, r, get_error_code_from_lib_error_code(r));
} }
break; break;
case NGHTTP2_PRIORITY: case NGHTTP2_PRIORITY:
r = nghttp2_frame_unpack_priority(&frame.priority, r = nghttp2_frame_unpack_priority(&frame->priority,
session->iframe.headbuf, session->iframe.headbuf,
sizeof(session->iframe.headbuf), sizeof(session->iframe.headbuf),
session->iframe.buf, session->iframe.buf,
session->iframe.buflen); session->iframe.buflen);
if(r == 0) { if(r == 0) {
r = nghttp2_session_on_priority_received(session, &frame); r = nghttp2_session_on_priority_received(session, frame);
nghttp2_frame_priority_free(&frame.priority); if(r != NGHTTP2_ERR_PAUSE) {
nghttp2_frame_priority_free(&frame->priority);
}
} else if(nghttp2_is_non_fatal(r)) { } else if(nghttp2_is_non_fatal(r)) {
r = nghttp2_session_handle_parse_error(session, type, r, r = nghttp2_session_handle_parse_error(session, type, r,
NGHTTP2_PROTOCOL_ERROR); NGHTTP2_PROTOCOL_ERROR);
} }
break; break;
case NGHTTP2_RST_STREAM: 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, session->iframe.headbuf,
sizeof(session->iframe.headbuf), sizeof(session->iframe.headbuf),
session->iframe.buf, session->iframe.buf,
session->iframe.buflen); session->iframe.buflen);
if(r == 0) { if(r == 0) {
r = nghttp2_session_on_rst_stream_received(session, &frame); r = nghttp2_session_on_rst_stream_received(session, frame);
nghttp2_frame_rst_stream_free(&frame.rst_stream); if(r != NGHTTP2_ERR_PAUSE) {
nghttp2_frame_rst_stream_free(&frame->rst_stream);
}
} else if(nghttp2_is_non_fatal(r)) { } else if(nghttp2_is_non_fatal(r)) {
r = nghttp2_session_handle_parse_error(session, type, r, r = nghttp2_session_handle_parse_error(session, type, r,
NGHTTP2_PROTOCOL_ERROR); NGHTTP2_PROTOCOL_ERROR);
} }
break; break;
case NGHTTP2_SETTINGS: case NGHTTP2_SETTINGS:
r = nghttp2_frame_unpack_settings(&frame.settings, r = nghttp2_frame_unpack_settings(&frame->settings,
session->iframe.headbuf, session->iframe.headbuf,
sizeof(session->iframe.headbuf), sizeof(session->iframe.headbuf),
session->iframe.buf, session->iframe.buf,
session->iframe.buflen); session->iframe.buflen);
if(r == 0) { if(r == 0) {
r = nghttp2_session_on_settings_received(session, &frame); r = nghttp2_session_on_settings_received(session, frame);
nghttp2_frame_settings_free(&frame.settings); if(r != NGHTTP2_ERR_PAUSE) {
nghttp2_frame_settings_free(&frame->settings);
}
} else if(nghttp2_is_non_fatal(r)) { } else if(nghttp2_is_non_fatal(r)) {
r = nghttp2_session_handle_parse_error(session, type, r, r = nghttp2_session_handle_parse_error(session, type, r,
NGHTTP2_PROTOCOL_ERROR); NGHTTP2_PROTOCOL_ERROR);
@ -2588,7 +2641,7 @@ static int nghttp2_session_process_ctrl_frame(nghttp2_session *session)
break; break;
case NGHTTP2_PUSH_PROMISE: case NGHTTP2_PUSH_PROMISE:
if(session->iframe.error_code == 0) { 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, session->iframe.headbuf,
sizeof(session->iframe.headbuf), sizeof(session->iframe.headbuf),
session->iframe.buf, session->iframe.buf,
@ -2598,51 +2651,59 @@ static int nghttp2_session_process_ctrl_frame(nghttp2_session *session)
r = session->iframe.error_code; r = session->iframe.error_code;
} }
if(r == 0) { if(r == 0) {
r = nghttp2_session_on_push_promise_received(session, &frame); r = nghttp2_session_on_push_promise_received(session, frame);
nghttp2_frame_push_promise_free(&frame.push_promise); if(r != NGHTTP2_ERR_PAUSE) {
nghttp2_hd_end_headers(&session->hd_inflater); nghttp2_frame_push_promise_free(&frame->push_promise);
nghttp2_hd_end_headers(&session->hd_inflater);
}
} else if(nghttp2_is_non_fatal(r)) { } else if(nghttp2_is_non_fatal(r)) {
r = nghttp2_session_handle_parse_error r = nghttp2_session_handle_parse_error
(session, type, r, get_error_code_from_lib_error_code(r)); (session, type, r, get_error_code_from_lib_error_code(r));
} }
break; break;
case NGHTTP2_PING: case NGHTTP2_PING:
r = nghttp2_frame_unpack_ping(&frame.ping, r = nghttp2_frame_unpack_ping(&frame->ping,
session->iframe.headbuf, session->iframe.headbuf,
sizeof(session->iframe.headbuf), sizeof(session->iframe.headbuf),
session->iframe.buf, session->iframe.buf,
session->iframe.buflen); session->iframe.buflen);
if(r == 0) { if(r == 0) {
r = nghttp2_session_on_ping_received(session, &frame); r = nghttp2_session_on_ping_received(session, frame);
nghttp2_frame_ping_free(&frame.ping); if(r != NGHTTP2_ERR_PAUSE) {
nghttp2_frame_ping_free(&frame->ping);
}
} else if(nghttp2_is_non_fatal(r)) { } else if(nghttp2_is_non_fatal(r)) {
r = nghttp2_session_handle_parse_error(session, type, r, r = nghttp2_session_handle_parse_error(session, type, r,
NGHTTP2_PROTOCOL_ERROR); NGHTTP2_PROTOCOL_ERROR);
} }
break; break;
case NGHTTP2_GOAWAY: case NGHTTP2_GOAWAY:
r = nghttp2_frame_unpack_goaway(&frame.goaway, r = nghttp2_frame_unpack_goaway(&frame->goaway,
session->iframe.headbuf, session->iframe.headbuf,
sizeof(session->iframe.headbuf), sizeof(session->iframe.headbuf),
session->iframe.buf, session->iframe.buf,
session->iframe.buflen); session->iframe.buflen);
if(r == 0) { if(r == 0) {
r = nghttp2_session_on_goaway_received(session, &frame); r = nghttp2_session_on_goaway_received(session, frame);
nghttp2_frame_goaway_free(&frame.goaway); if(r != NGHTTP2_ERR_PAUSE) {
nghttp2_frame_goaway_free(&frame->goaway);
}
} else if(nghttp2_is_non_fatal(r)) { } else if(nghttp2_is_non_fatal(r)) {
r = nghttp2_session_handle_parse_error(session, type, r, r = nghttp2_session_handle_parse_error(session, type, r,
NGHTTP2_PROTOCOL_ERROR); NGHTTP2_PROTOCOL_ERROR);
} }
break; break;
case NGHTTP2_WINDOW_UPDATE: 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, session->iframe.headbuf,
sizeof(session->iframe.headbuf), sizeof(session->iframe.headbuf),
session->iframe.buf, session->iframe.buf,
session->iframe.buflen); session->iframe.buflen);
if(r == 0) { if(r == 0) {
r = nghttp2_session_on_window_update_received(session, &frame); r = nghttp2_session_on_window_update_received(session, frame);
nghttp2_frame_window_update_free(&frame.window_update); if(r != NGHTTP2_ERR_PAUSE) {
nghttp2_frame_window_update_free(&frame->window_update);
}
} else if(nghttp2_is_non_fatal(r)) { } else if(nghttp2_is_non_fatal(r)) {
r = nghttp2_session_handle_parse_error(session, type, r, r = nghttp2_session_handle_parse_error(session, type, r,
NGHTTP2_PROTOCOL_ERROR); 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; return r;
} else { } else {
return 0; return 0;
@ -2884,6 +2948,73 @@ static int nghttp2_session_check_data_recv_allowed(nghttp2_session *session,
return 0; 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, ssize_t nghttp2_session_mem_recv(nghttp2_session *session,
const uint8_t *in, size_t inlen) 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); memcpy(session->iframe.buf+session->iframe.off, inmark, readlen);
} }
} else { } 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; session->iframe.off += readlen;
inmark += 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.payloadlen == session->iframe.off) {
if(session->iframe.error_code != NGHTTP2_ERR_FRAME_TOO_LARGE) { if(session->iframe.error_code != NGHTTP2_ERR_FRAME_TOO_LARGE) {
@ -3030,13 +3169,16 @@ ssize_t nghttp2_session_mem_recv(nghttp2_session *session,
} else { } else {
r = nghttp2_session_process_data_frame(session); r = nghttp2_session_process_data_frame(session);
} }
if(r == NGHTTP2_ERR_PAUSE) {
return inmark - in;
}
if(r < 0) { if(r < 0) {
/* FATAL */ /* FATAL */
assert(r < NGHTTP2_ERR_FATAL); assert(r < NGHTTP2_ERR_FATAL);
return r; return r;
} }
} }
nghttp2_inbound_frame_reset(&session->iframe); nghttp2_inbound_frame_reset(session);
} }
} }
} }

View File

@ -80,6 +80,7 @@ typedef enum {
} nghttp2_inbound_state; } nghttp2_inbound_state;
typedef struct { typedef struct {
nghttp2_frame frame;
nghttp2_inbound_state state; nghttp2_inbound_state state;
uint8_t headbuf[NGHTTP2_FRAME_HEAD_LENGTH]; uint8_t headbuf[NGHTTP2_FRAME_HEAD_LENGTH];
/* How many bytes are filled in headbuf */ /* How many bytes are filled in headbuf */

View File

@ -86,6 +86,7 @@ int main(int argc, char* argv[])
test_nghttp2_session_recv_data) || test_nghttp2_session_recv_data) ||
!CU_add_test(pSuite, "session_recv_frame_too_large", !CU_add_test(pSuite, "session_recv_frame_too_large",
test_nghttp2_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", !CU_add_test(pSuite, "session_add_frame",
test_nghttp2_session_add_frame) || test_nghttp2_session_add_frame) ||
!CU_add_test(pSuite, "session_on_request_headers_received", !CU_add_test(pSuite, "session_on_request_headers_received",

View File

@ -67,6 +67,7 @@ typedef struct {
size_t block_count; size_t block_count;
int data_chunk_recv_cb_called; int data_chunk_recv_cb_called;
int data_recv_cb_called; int data_recv_cb_called;
const nghttp2_frame *frame;
} my_user_data; } my_user_data;
static void scripted_data_feed_init(scripted_data_feed *df, 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; 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, static int on_invalid_frame_recv_callback(nghttp2_session *session,
const nghttp2_frame *frame, const nghttp2_frame *frame,
nghttp2_error_code error_code, nghttp2_error_code error_code,
@ -177,6 +188,16 @@ static int on_data_chunk_recv_callback(nghttp2_session *session,
return 0; 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, static int on_data_recv_callback(nghttp2_session *session,
uint16_t length, uint8_t flags, uint16_t length, uint8_t flags,
int32_t stream_id, void *user_data) int32_t stream_id, void *user_data)
@ -631,6 +652,117 @@ void test_nghttp2_session_recv_data(void)
nghttp2_session_del(session); 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) void test_nghttp2_session_add_frame(void)
{ {
nghttp2_session *session; nghttp2_session *session;

View File

@ -31,6 +31,7 @@ void test_nghttp2_session_recv_invalid_frame(void);
void test_nghttp2_session_recv_eof(void); void test_nghttp2_session_recv_eof(void);
void test_nghttp2_session_recv_data(void); void test_nghttp2_session_recv_data(void);
void test_nghttp2_session_recv_frame_too_large(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_add_frame(void);
void test_nghttp2_session_on_request_headers_received(void); void test_nghttp2_session_on_request_headers_received(void);
void test_nghttp2_session_on_response_headers_received(void); void test_nghttp2_session_on_response_headers_received(void);